├── .npmignore
├── .gitignore
├── test
├── expected
│ ├── target1
│ ├── target2
│ └── target3
├── input
│ └── default_options
├── fixtures
│ ├── test_robots.txt
│ └── test_robots_sitemap.txt
├── utils.js
├── html_snapshots_target1.js
├── html_snapshots_target3.js
├── html_snapshots_target2.js
├── server
│ ├── sitemap.xml
│ ├── index.js
│ ├── contact
│ │ └── index.html
│ ├── index.html
│ └── services
│ │ └── carpets
│ │ └── index.html
├── helpers
│ └── options.js
└── html_snapshots_test.js
├── eslint.config.js
├── .github
└── workflows
│ ├── verify.yml
│ └── deploy.yml
├── LICENSE.md
├── tasks
└── html_snapshots.js
├── package.json
├── Gruntfile.js
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | .github
2 | /node_modules
3 | /test
4 | /tmp
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | tmp
4 | grunt-html-snapshots.sublime-project
5 | grunt-html-snapshots.sublime-workspace
--------------------------------------------------------------------------------
/test/expected/target1:
--------------------------------------------------------------------------------
1 | tmp/target1/snapshots/index.html
2 | tmp/target1/snapshots/contact/index.html
3 | tmp/target1/snapshots/services/carpets/index.html
--------------------------------------------------------------------------------
/test/expected/target2:
--------------------------------------------------------------------------------
1 | tmp/target2/snapshots/index.html
2 | tmp/target2/snapshots/contact/index.html
3 | tmp/target2/snapshots/services/carpets/index.html
--------------------------------------------------------------------------------
/test/expected/target3:
--------------------------------------------------------------------------------
1 | tmp/target3/snapshots/index.html
2 | tmp/target3/snapshots/contact/index.html
3 | tmp/target3/snapshots/services/carpets/index.html
--------------------------------------------------------------------------------
/test/input/default_options:
--------------------------------------------------------------------------------
1 | {
2 | "source": "test/fixtures/test_robots.txt",
3 | "selector": "#dynamic-content",
4 | "outputDirClean": "true",
5 | "hostname": "localhost"
6 | }
--------------------------------------------------------------------------------
/test/fixtures/test_robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 |
3 | Allow: /
4 | Allow: /contact
5 | Allow: /services/carpets
6 |
7 | Disallow: /api/
8 | Disallow: /vendor/
9 | Disallow: /fonts/
10 | Disallow: /images/
11 |
12 | User-agent: ia_archiver
13 | Disallow: /
14 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * test utils
3 | */
4 | const fs = require('fs').promises;
5 |
6 | function pathExists (path) {
7 | return fs.access(path)
8 | .then(() => true)
9 | .catch(() => false);
10 | }
11 |
12 | module.exports = {
13 | pathExists
14 | };
15 |
--------------------------------------------------------------------------------
/test/fixtures/test_robots_sitemap.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 |
3 | Allow: /
4 | Allow: /contact
5 | Allow: /services/carpets
6 |
7 | Disallow: /api/
8 | Disallow: /vendor/
9 | Disallow: /fonts/
10 | Disallow: /images/
11 |
12 | Sitemap: http://localhost:8051/sitemap.xml
13 |
14 | User-agent: ia_archiver
15 | Disallow: /
16 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | const js = require('@eslint/js');
2 | const globals = require('globals');
3 |
4 | module.exports = [{
5 | ignores: [
6 | 'node_modules/**',
7 | 'tmp/**'
8 | ]
9 | }, {
10 | files: [
11 | '**/*'
12 | ],
13 | ...js.configs.recommended,
14 | languageOptions: {
15 | globals: {
16 | ...globals.node
17 | }
18 | }
19 | }];
20 |
--------------------------------------------------------------------------------
/test/html_snapshots_target1.js:
--------------------------------------------------------------------------------
1 | /**
2 | * tests, target1
3 | */
4 | const grunt = require('grunt');
5 | const { pathExists } = require('./utils');
6 |
7 | exports.html_snapshots = {
8 | setUp: done => {
9 | // setup here if necessary
10 | done();
11 | },
12 |
13 | target1: test => {
14 | test.expect(3);
15 |
16 | const results = [];
17 |
18 | grunt.file.read('test/expected/target1').split('\n').forEach(line => {
19 | results.push(pathExists(line).then(exists => {
20 | test.equal(true, exists, `${line} should exist`);
21 | }));
22 | });
23 |
24 | Promise.all(results).finally(()=> {
25 | test.done();
26 | })
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/test/html_snapshots_target3.js:
--------------------------------------------------------------------------------
1 | /**
2 | * tests, target1
3 | */
4 | const grunt = require('grunt');
5 | const { pathExists } = require('./utils');
6 |
7 | exports.html_snapshots = {
8 | setUp: done => {
9 | // setup here if necessary
10 | done();
11 | },
12 |
13 | target1: test => {
14 | test.expect(3);
15 |
16 | const results = [];
17 |
18 | grunt.file.read('test/expected/target3').split('\n').forEach(line => {
19 | results.push(pathExists(line).then(exists => {
20 | test.equal(true, exists, `${line} should exist`);
21 | }));
22 | });
23 |
24 | Promise.all(results).finally(()=> {
25 | test.done();
26 | })
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/test/html_snapshots_target2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * tests, target2
3 | */
4 | const grunt = require('grunt');
5 | const { pathExists } = require('./utils');
6 |
7 | exports.html_snapshots = {
8 | setUp: done => {
9 | // setup here if necessary
10 | done();
11 | },
12 |
13 | target2: test => {
14 | test.expect(3);
15 |
16 | const results = [];
17 |
18 | grunt.file.read('test/expected/target2').split('\n').forEach(line => {
19 | results.push(pathExists(line).then(exists => {
20 | test.equal(false, exists, `${line} should not exist`);
21 | }));
22 | });
23 |
24 | Promise.all(results).finally(() => {
25 | test.done();
26 | });
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/test/server/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://localhost:8051/
5 | 2012-08-11T12:16:09+00:00
6 | daily
7 | 1.0
8 |
9 |
10 | http://localhost:8051/contact
11 | 2012-11-02T02:53:40+00:00
12 | weekly
13 | 0.6
14 |
15 |
16 | http://localhost:8051/services/carpets
17 | 2012-10-19T10:45:23+00:00
18 | weekly
19 | 0.6
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.github/workflows/verify.yml:
--------------------------------------------------------------------------------
1 | name: Verify
2 | on:
3 | pull_request:
4 | branches: [ master ]
5 |
6 | jobs:
7 | verify:
8 | runs-on: ubuntu-22.04
9 | strategy:
10 | matrix:
11 | node-version: [20.x, 22.x, 24.x]
12 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
13 |
14 | steps:
15 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # 6.0.1
16 | - name: Use Node.js ${{ matrix.node-version }}
17 | uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # 6.1.0
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 | - run: npm ci
21 | - name: Lint
22 | run: npm run lint
23 | - name: Test
24 | if: ${{ success() }}
25 | run: npm test
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 | on:
3 | push:
4 | branches: [ master ]
5 |
6 | jobs:
7 | deploy:
8 | runs-on: ubuntu-22.04
9 | permissions:
10 | contents: read
11 | id-token: write
12 | steps:
13 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # 6.0.1
14 | - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # 6.1.0
15 | with:
16 | node-version: '24.x'
17 | registry-url: 'https://registry.npmjs.org'
18 | - name: Update npm
19 | run: npm install -g npm@latest # ensure npm 11.5.1 or later
20 | - run: npm ci
21 | - name: Test
22 | run: npm test
23 | - name: Publish
24 | if: ${{ success() }}
25 | run: npm publish --access public
--------------------------------------------------------------------------------
/test/helpers/options.js:
--------------------------------------------------------------------------------
1 | /**
2 | * options.js
3 | *
4 | * A test helper to detect which html-snapshots options to use
5 | *
6 | * phantomjs
7 | * If a global phantomjs is defined, decorates html-snapshots options to specify that global
8 | * In some test environments (travis), local phantomjs will not install if a global is found.
9 | */
10 |
11 | const spawn = require("child_process").spawn;
12 |
13 | module.exports = {
14 |
15 | // for now, callback is passed true if global phantomjs should be used
16 | detector: callback => {
17 | // try to run phantom globally
18 | const cp = spawn("phantomjs", ["--version"]);
19 |
20 | // if it fails, use local per the defaults
21 | cp.on("error", () => {
22 | callback(false);
23 | });
24 |
25 | // if it succeeds, use the global
26 | cp.on("exit", code => {
27 | if (code === 0) {
28 | callback(true);
29 | }
30 | });
31 | }
32 | };
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 - 2025 Alex Grant
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/tasks/html_snapshots.js:
--------------------------------------------------------------------------------
1 | /**
2 | * grunt-html-snapshots
3 | * https://github.com/localnerve/grunt-html-snapshots
4 | *
5 | * Copyright (c) 2013 - 2025, LocalNerve, Alex Grant
6 | * Licensed under the MIT license.
7 | */
8 | const html_snapshots = require('html-snapshots');
9 |
10 | module.exports = function (grunt) {
11 | grunt.registerMultiTask('html_snapshots', 'Generate html snapshots.', function () {
12 | // Merge task-specific and/or target-specific options with these defaults.
13 | const options = this.options({
14 | force: false
15 | });
16 |
17 | // Report html_snapshot errors but dont fail the task
18 | const force = options.force;
19 | delete options.force;
20 |
21 | grunt.verbose.writeflags(options, 'html_snapshots options');
22 |
23 | // Setup for async
24 | const done = this.async();
25 |
26 | // Take the snapshots
27 | html_snapshots.run(options)
28 | .then(() => {
29 | grunt.log.ok();
30 | })
31 | .catch(err => {
32 | grunt.log.error('html_snapshots failed');
33 | if (force) {
34 | grunt.log.error(err);
35 | }
36 | return err;
37 | })
38 | .then(err => {
39 | done(force ? undefined : err);
40 | });
41 | });
42 | };
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grunt-html-snapshots",
3 | "description": "The grunt task for html-snapshots",
4 | "version": "9.2.0",
5 | "homepage": "https://github.com/localnerve/grunt-html-snapshots",
6 | "author": {
7 | "name": "Alex Grant",
8 | "email": "alex@localnerve.com",
9 | "url": "http://www.localnerve.com"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/localnerve/grunt-html-snapshots.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/localnerve/grunt-html-snapshots/issues"
17 | },
18 | "license": "MIT",
19 | "main": "Gruntfile.js",
20 | "engines": {
21 | "node": ">= 20"
22 | },
23 | "files": [
24 | "Gruntfile.js",
25 | "tasks/*"
26 | ],
27 | "scripts": {
28 | "test": "grunt test",
29 | "lint": "eslint ."
30 | },
31 | "dependencies": {
32 | "html-snapshots": "^5.2.0"
33 | },
34 | "devDependencies": {
35 | "@eslint/js": "^9.39.2",
36 | "eslint": "^9.39.2",
37 | "globals": "^16.5.0",
38 | "grunt-contrib-clean": "^2.0.1",
39 | "grunt-contrib-nodeunit": "^5.0.0",
40 | "grunt": "^1.6.1"
41 | },
42 | "overrides": {
43 | "tap": "21.5.0",
44 | "request": "npm:@cypress/request@3.0.9"
45 | },
46 | "peerDependencies": {
47 | "grunt": ">=1.0.0"
48 | },
49 | "keywords": [
50 | "SEO",
51 | "html",
52 | "snapshots",
53 | "selector",
54 | "robots.txt",
55 | "sitemap.xml",
56 | "gruntplugin"
57 | ]
58 | }
59 |
--------------------------------------------------------------------------------
/test/html_snapshots_test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test the task
3 | */
4 |
5 | const grunt = require('grunt');
6 | const { pathExists } = require('./utils');
7 |
8 | exports.html_snapshots = {
9 | setUp: function (done) {
10 | // setup here if necessary
11 | done();
12 | },
13 |
14 | target1: function(test) {
15 | test.expect(3);
16 |
17 | const results = [];
18 |
19 | grunt.file.read('test/expected/target1').split('\n').forEach(line => {
20 | results.push(pathExists(line).then(exists => {
21 | test.equal(true, exists, `${line} should exist`);
22 | }));
23 | });
24 |
25 | Promise.all(results).finally(() => {
26 | test.done();
27 | });
28 | },
29 |
30 | target2: function(test) {
31 | test.expect(3);
32 |
33 | const results = [];
34 |
35 | grunt.file.read('test/expected/target2').split('\n').forEach(line => {
36 | results.push(pathExists(line).then(exists => {
37 | test.equal(false, exists, `${line} should not exist`);
38 | }));
39 | });
40 |
41 | Promise.all(results).finally(() => {
42 | test.done();
43 | });
44 | },
45 |
46 | target3: function(test) {
47 | test.expect(3);
48 |
49 | const results = [];
50 |
51 | grunt.file.read('test/expected/target3').split('\n').forEach(line => {
52 | results.push(pathExists(line).then(exists => {
53 | test.equal(true, exists, `${line} should exist`);
54 | }));
55 | });
56 |
57 | Promise.all(results).finally(() => {
58 | test.done();
59 | });
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/test/server/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create a local web server for tests
3 | */
4 | const http = require("http"),
5 | url = require("url"),
6 | path = require("path"),
7 | fs = require("fs");
8 |
9 | module.exports = {
10 |
11 | start: function(rootDir, port, end) {
12 | http.createServer(function(request, response) {
13 |
14 | const uri = url.parse(request.url).pathname;
15 | let filename = path.join(rootDir, uri);
16 |
17 | fs.exists(filename, function(exists) {
18 | if(!exists) {
19 | response.writeHead(404, {"Content-Type": "text/plain"});
20 | response.write("404 Not Found\n");
21 | response.end();
22 | return;
23 | }
24 |
25 | if (fs.statSync(filename).isDirectory()) filename += '/index.html';
26 |
27 | fs.readFile(filename, "binary", function(err, file) {
28 | if(err) {
29 | response.writeHead(500, {"Content-Type": "text/plain"});
30 | response.write(err + "\n");
31 | response.end();
32 | if (typeof end === "function")
33 | setTimeout(end, 1000);
34 | return;
35 | }
36 |
37 | if (path.extname(filename) === '.xml') {
38 | response.writeHead(200, {"Content-Type": "text/xml"});
39 | response.write(file);
40 | } else {
41 | response.writeHead(200);
42 | response.write(file, "binary");
43 | }
44 | response.end();
45 | if (typeof end === "function")
46 | setTimeout(end, 1000);
47 | });
48 | });
49 | }).listen(parseInt(port, 10));
50 | }
51 | };
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * grunt-html-snapshots
3 | * https://github.com/localnerve/grunt-html-snapshots
4 | *
5 | * Copyright (c) 2013 - 2025 Alex Grant
6 | * Licensed under the MIT license.
7 | */
8 |
9 | module.exports = function (grunt) {
10 |
11 | // Project configuration.
12 | grunt.initConfig({
13 | // Before generating any new files, remove any previously-created files.
14 | clean: {
15 | tests: ['tmp'],
16 | },
17 |
18 | test_server1: {
19 | options: {
20 | port: 8051
21 | }
22 | },
23 |
24 | test_server2: {
25 | options: {
26 | port: 8052
27 | }
28 | },
29 |
30 | test_server3: {
31 | options: {
32 | port: 8051
33 | }
34 | },
35 |
36 | // Configuration to be run (and then tested).
37 | html_snapshots: {
38 | options: grunt.file.readJSON("test/input/default_options"),
39 |
40 | target1: {
41 | options: {
42 | outputDir: "tmp/target1/snapshots",
43 | port: "<%= test_server1.options.port %>"
44 | }
45 | },
46 |
47 | target2: {
48 | options: {
49 | outputDir: "tmp/target2/snapshots",
50 | hostname: "bogus",
51 | port: "<%= test_server2.options.port %>",
52 | force: true
53 | }
54 | },
55 |
56 | target3: {
57 | options: {
58 | source: "test/fixtures/test_robots_sitemap.txt",
59 | outputDir: "tmp/target3/snapshots",
60 | port: "<%= test_server3.options.port %>"
61 | }
62 | }
63 | },
64 |
65 | // Unit tests.
66 | nodeunit: {
67 | tests: ['test/*_test.js'],
68 | target1: ['test/html_snapshots_target1.js'],
69 | target2: ['test/html_snapshots_target2.js']
70 | },
71 |
72 | });
73 |
74 | // Actually load this plugin's task(s).
75 | grunt.loadTasks('tasks');
76 |
77 | // These plugins provide necessary tasks.
78 | grunt.loadNpmTasks('grunt-contrib-clean');
79 | grunt.loadNpmTasks('grunt-contrib-nodeunit');
80 |
81 | // Register the test server
82 | grunt.registerTask('test_server1', 'serve the test files', function(){
83 | const options = this.options();
84 | const server = require('./test/server');
85 | server.start('./test/server', options.port);
86 | grunt.log.writeln("running test server on port "+options.port);
87 | grunt.log.ok();
88 | });
89 |
90 | grunt.registerTask('html_snapshots_options', 'prepare options for html_snapshots', function(){
91 | const optionsHelper = require("./test/helpers/options");
92 | const done = this.async();
93 | optionsHelper.detector(globalPhantom => {
94 | if (globalPhantom) {
95 | var htmlSnapshots = grunt.config.get("html_snapshots");
96 | htmlSnapshots.options.phantomjs = "phantomjs";
97 | grunt.config.set("html_snapshots", htmlSnapshots);
98 | }
99 | done();
100 | });
101 | });
102 |
103 | // Whenever the "test" task is run, run this plugin's task(s), then test the result.
104 | grunt.registerTask('test', ['clean', 'test_server1', 'html_snapshots_options', 'html_snapshots', 'nodeunit:tests']);
105 | grunt.registerTask('target1', ['clean', 'test_server1', 'html_snapshots_options', 'html_snapshots:target1', 'nodeunit:target1']);
106 | grunt.registerTask('target2', ['clean', 'html_snapshots_options', 'html_snapshots:target2', 'nodeunit:target2']);
107 |
108 | // By default, lint and run all tests.
109 | grunt.registerTask('default', ['test']);
110 |
111 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # grunt-html-snapshots
2 |
3 | [](http://badge.fury.io/js/grunt-html-snapshots)
4 | 
5 |
6 | > The grunt task for [html-snapshots](http://github.com/localnerve/html-snapshots)
7 |
8 | ## Getting Started
9 | This plugin requires Grunt `>=1.0.0`
10 | *To use an older Grunt, use this library at tag `#v1.0.2`*
11 |
12 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
13 |
14 | ```shell
15 | npm install grunt-html-snapshots --save-dev
16 | ```
17 |
18 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
19 |
20 | ```js
21 | grunt.loadNpmTasks('grunt-html-snapshots');
22 | ```
23 |
24 | ## The "html_snapshots" task
25 |
26 | ### Overview
27 | This is a simple grunt task that uses the [html-snapshots](http://github.com/localnerve/html-snapshots) library. In your project's Gruntfile, add a section named `html_snapshots` to the data object passed into `grunt.initConfig()`.
28 |
29 | ```js
30 | grunt.initConfig({
31 | html_snapshots: {
32 | options: {
33 | // Task-specific options go here.
34 | },
35 | your_target: {
36 | options: {
37 | // Target-specific options go here.
38 | }
39 | },
40 | },
41 | })
42 | ```
43 |
44 | ### Options
45 |
46 | #### options.force
47 | Type: `boolean`
48 | Default value: `false`
49 |
50 | A boolean value that is used to force the Gruntfile to continue, even if this task fails.
51 |
52 | #### MORE
53 | For details of task and target specific options, read the options section of [html-snapshots](http://github.com/localnerve/html-snapshots)
54 |
55 | ### Usage Examples
56 |
57 | #### Default Options and Targets
58 | In this example, the default options are used to specify that the output directory should always be cleaned prior to taking snapshots, that a local robots.txt file should be used, and the html should be served from localhost. On all pages except the home page, when the selector "#dynamic-content" appears in the output, the page is ready for a snapshot. On the home page, we only take the snapshot when the selector "#home-content" is visible in the output.
59 |
60 | ```js
61 | grunt.initConfig({
62 | html_snapshots: {
63 | // options for all targets
64 | options: {
65 | source: "/path/to/local/robots.txt",
66 | hostname: "localhost",
67 | selector: { "__default": "#dynamic-content", "/": "#home-content" },
68 | outputDirClean: "true",
69 | },
70 | // the debug target
71 | debug: {
72 | options: {
73 | outputDir: "./snapshots/debug"
74 | }
75 | },
76 | // the release target
77 | release: {
78 | options: {
79 | outputDir: "./snapshots/release"
80 | }
81 | }
82 | }
83 | });
84 |
85 | grunt.loadNpmTasks('grunt-html-snapshots');
86 |
87 | grunt.registerTask('debug', ['html_snapshots:debug']);
88 | grunt.registerTask('release', ['html_snapshots:release']);
89 | ```
90 |
91 | Many more options are available. For details and examples of using the html-snapshots options, visit [html-snapshots](http://github.com/localnerve/html-snapshots).
92 |
93 | ## Contributing
94 | In lieu of a formal style guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
95 |
96 | ## License
97 | [MIT License](LICENSE.md)
--------------------------------------------------------------------------------
/test/server/contact/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Contact - North Star Cleaning Service
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
122 |
123 |
124 |
125 |
Contact us instructions go here. All form inputs are required.
126 |
147 |
148 |
149 |
150 |
215 |
216 |
217 |
--------------------------------------------------------------------------------
/test/server/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | North Star Cleaning Service
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
122 |
123 |
124 |
125 |
126 |
127 |

128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
151 |
163 |
175 |
176 |
177 |
242 |
243 |
244 |
--------------------------------------------------------------------------------
/test/server/services/carpets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Carpet Cleaning - North Star Cleaning Service
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
122 |
123 |
124 |
125 |

126 |
127 |
128 |
132 |
133 |
134 |
135 |
151 |
152 |
168 |
169 |
234 |
235 |
236 |
237 |

238 |
×
239 |
240 |

241 |
×
242 |
243 |

244 |
×
245 |
246 |

247 |
×
248 |
--------------------------------------------------------------------------------