generator-nitro 7.3.0 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/generators/app/templates/CUTAWAY.gitignore +3 -1
  2. package/generators/app/templates/CUTAWAYpackage.json +26 -20
  3. package/generators/app/templates/config/default.js +0 -6
  4. package/generators/app/templates/config/webpack/options.js +2 -5
  5. package/generators/app/templates/project/docs/nitro-config.md +0 -16
  6. package/generators/app/templates/project/docs/nitro.md +1 -1
  7. package/generators/app/templates/tests/backstop/engine_scripts/playwright/clickAndHoverHelper.js +43 -0
  8. package/generators/app/templates/tests/backstop/engine_scripts/playwright/interceptImages.js +31 -0
  9. package/generators/app/templates/tests/backstop/engine_scripts/playwright/loadCookies.js +16 -0
  10. package/generators/app/templates/tests/backstop/engine_scripts/playwright/onBefore.js +3 -0
  11. package/generators/app/templates/tests/backstop/engine_scripts/playwright/onReady.js +6 -0
  12. package/generators/app/templates/tests/backstop/engine_scripts/playwright/overrideCSS.js +27 -0
  13. package/generators/app/templates/tests/backstop/engine_scripts/puppet/clickAndHoverHelper.js +39 -39
  14. package/generators/app/templates/tests/backstop/engine_scripts/puppet/ignoreCSP.js +1 -1
  15. package/generators/app/templates/tests/cypress/cypress/e2e/pages/404.cy.js +9 -6
  16. package/generators/app/templates/tests/cypress/cypress/e2e/pages/index.cy.js +5 -5
  17. package/generators/app/templates/tests/cypress/readme.md +2 -2
  18. package/generators/app/templates/tests/playwright/e2e/pages/404.spec.ts +41 -0
  19. package/generators/app/templates/tests/playwright/e2e/pages/index.spec.ts +42 -0
  20. package/generators/app/templates/tests/playwright/playwright.config.ts +108 -0
  21. package/generators/app/templates/tests/playwright/readme.md +25 -0
  22. package/package.json +4 -4
@@ -40,11 +40,13 @@ debug.log
40
40
  /project/tmp
41
41
  /public/assets
42
42
  /public/proto
43
- /public/reports
43
+ /public/reports/**/*
44
+ !/public/reports/lighthouse/
44
45
  /src/patterns/**/template/*.js
45
46
  /src/patterns/**/template/partial/*.js
46
47
  /tests/cypress/cypress/screenshots
47
48
  /tests/cypress/cypress/videos
49
+ /tests/playwright/test-results/
48
50
 
49
51
  # allow
50
52
  !.gitkeep
@@ -6,8 +6,8 @@
6
6
  "private": true,
7
7
  "author": "The Nitro Team",
8
8
  "engines": {
9
- "node": ">=14.15.0 <19",
10
- "npm": ">=6.14.8 <10"
9
+ "node": ">=14.15.0 <19",
10
+ "npm": ">=6.14.8 <10"
11
11
  },
12
12
  "scripts": {
13
13
  "\n# PROJECT ------- ": "",
@@ -23,25 +23,30 @@
23
23
  "postinstall": "<% if (git.root) { %>cd <%= git.root %> && husky install <% if (git.project) { %><%= git.project %>/.husky && cd <%= git.project %> <% } %>&& <% } %><% if (options.themes) { %>npm rebuild node-sass && <% } %>env-linter<% if (git.root) { %> --hooksInstalled<% } %> --saveExact --dependenciesExactVersion --lts",
24
24
  "\n# LINT/TEST ------- ": "",
25
25
  "check-node-version": "check-node-version --print --package",
26
- "cypress-test": "npm run build && cross-env PORT=8888 NITRO_MODE=test npm-run-all --parallel --race test:cypress:serve cypress-test:open",
26
+ "cypress-test": "npm run build && cross-env PORT=8888 NITRO_MODE=test npm-run-all --parallel --race prod:serve cypress-test:open",
27
27
  "cypress-test:open": "cypress open --project ./tests/cypress/ --e2e --browser chrome",
28
- "lighthouse-test": "npm run build && cross-env PORT=8890 NITRO_MODE=test npm-run-all --parallel --race lighthouse-test:*",
29
- "lighthouse-test:serve": "npm run prod:serve",
30
- "lighthouse-test:run": "lighthouse http://localhost:8890/<% if (options.exampleCode) { %>example-patterns<% } else { %>index<% } %> --quiet --configPath=./tests/lighthouse/lighthouse.config.js --output-path=./public/reports/lighthouse/report.html --view",
28
+ "lighthouse-test": "npm run build && cross-env PORT=8889 NITRO_MODE=test npm-run-all --parallel --race prod:serve lighthouse-test:run",
29
+ "lighthouse-test:run": "lighthouse http://localhost:8889/<% if (options.exampleCode) { %>example-patterns<% } else { %>index<% } %> --quiet --configPath=./tests/lighthouse/lighthouse.config.js --output-path=./public/reports/lighthouse/report.html --view",
31
30
  "lint": "npm-run-all lint:*",
32
31
  "lint:css": "stylelint src/**/*.*ss --allow-empty-input",
33
32
  "lint:data": "nitro-app-validate-pattern-data",
34
33
  "lint:html": "gulp lint-html",
35
34
  "lint:js": "eslint ./src --ext <% if (options.jsCompiler === 'js') { %>.js,.jsx<% } else { %>.ts,.tsx<% } %>",
36
35
  "lint:license": "license-checker --production --summary --exclude \"Apache-2.0, BSD, ISC, LGPL, MIT, MPL\" --failOn \"AGPL; EPL; GPL\"",
36
+ "playwright-test": "cross-env PORT=8890 NITRO_MODE=test playwright test --config=tests/playwright/playwright.config.ts",
37
+ "playwright-test-generate": "cross-env PORT=8891 NITRO_MODE=test npm-run-all --parallel --race prod:serve playwright-test-generate:codegen",
38
+ "playwright-test-generate:codegen": "playwright codegen http://localhost:8891",
37
39
  "prettier": "prettier --write \"**/*.*(js|jsx|ts|tsx|json|md|mdx|graphql|gql|yml|yaml)\"",
38
40
  "test": "npm-run-all test:*",
39
41
  "test:lint": "npm run lint",
40
- "test:cypress": "npm run build && cross-env PORT=8888 NITRO_MODE=test npm-run-all --parallel --race test:cypress:*",
41
- "test:cypress:serve": "npm run prod:serve",
42
- "test:cypress:test": "cypress run --project ./tests/cypress/",
42
+ "test:build": "npm run build",
43
+ "test:e2e": "cross-env PORT=8899 NITRO_MODE=test npm-run-all --parallel --race test:e2e:*",
44
+ "test:e2e:serve": "npm run prod:serve",
45
+ "test:e2e:run": "npm-run-all --serial test:e2e:run:*",
46
+ "test:e2e:run:cypress": "cypress run --project ./tests/cypress/",
47
+ "test:e2e:run:playwright": "playwright test --config=tests/playwright/playwright.config.ts",
43
48
  "visual-approve": "backstop approve --config=tests/backstop/backstop.config.js --docker",
44
- "visual-test": "npm run build && cross-env PORT=8889 npm-run-all --parallel --race visual-test:*",
49
+ "visual-test": "npm run build && cross-env PORT=8892 npm-run-all --parallel --race visual-test:*",
45
50
  "visual-test:serve": "npm run prod:serve",
46
51
  "visual-test:test": "backstop test --config=tests/backstop/backstop.config.js --docker",
47
52
  "\n# BUILD/RELEASE ------- ": "",
@@ -84,7 +89,7 @@
84
89
  "@gondel/core": "1.2.7",
85
90
  "@gondel/plugin-hot": "1.2.7",
86
91
  "bootstrap": "5.2.3",<% } %>
87
- "core-js": "3.29.0"<% if (options.exampleCode) { %>,
92
+ "core-js": "3.30.0"<% if (options.exampleCode) { %>,
88
93
  "flatpickr": "4.6.13",
89
94
  "handlebars": "4.7.7",
90
95
  "jquery": "3.6.4",
@@ -95,7 +100,7 @@
95
100
  "svg4everybody": "2.1.9"<% } %>
96
101
  },
97
102
  "devDependencies": {<% if (options.jsCompiler === 'js') { %>
98
- "@babel/eslint-parser": "7.19.1",<% } %>
103
+ "@babel/eslint-parser": "7.21.3",<% } %>
99
104
  "@khanacademy/tota11y": "0.2.0",
100
105
  "@merkle-open/eslint-config": "1.0.0",
101
106
  "@merkle-open/html-validate-config": "1.0.1",
@@ -105,17 +110,18 @@
105
110
  "@nitro/app": "<%= version %>",
106
111
  "@nitro/exporter": "<%= version %>",
107
112
  "@nitro/gulp": "<%= version %>",
108
- "@nitro/webpack": "<%= version %>",<% if (options.jsCompiler === 'ts') { %>
113
+ "@nitro/webpack": "<%= version %>",
114
+ "@playwright/test": "1.32.2",<% if (options.jsCompiler === 'ts') { %>
109
115
  "@types/bootstrap": "5.2.6",<% if (options.exampleCode ) { %>
110
116
  "@types/jquery": "3.5.16",<% } %>
111
117
  "@types/svg4everybody": "2.1.2",
112
118
  "@types/webpack-env": "1.18.0",<% } %>
113
- "backstopjs": "6.1.4",
119
+ "backstopjs": "6.2.0",
114
120
  "check-node-version": "4.2.1",
115
121
  "commitizen": "4.3.0",
116
122
  "config": "3.3.9",
117
123
  "cross-env": "7.0.3",
118
- "cypress": "12.7.0",
124
+ "cypress": "12.9.0",
119
125
  "cz-conventional-changelog": "3.3.0",
120
126
  "env-linter": "1.0.0",
121
127
  "eslint": "7.32.0",
@@ -123,16 +129,16 @@
123
129
  "extend": "3.0.2",
124
130
  "generator-nitro": "<%= version %>",
125
131
  "gulp": "4.0.2",
132
+ "html-validate": "7.14.0",
126
133
  "husky": "8.0.3",
127
- "html-validate": "7.13.3",
128
134
  "license-checker": "25.0.1",
129
- "lighthouse": "10.0.2",
135
+ "lighthouse": "10.1.0",
130
136
  "lint-staged": "13.2.0",<% if (options.themes) { %>
131
137
  "node-sass": "8.0.0",<% } %>
132
- "npm-check-updates": "16.7.12",
138
+ "npm-check-updates": "16.10.1",
133
139
  "npm-run-all": "4.1.5",
134
- "prettier": "2.8.4",
135
- "rimraf": "4.4.0",
140
+ "prettier": "2.8.7",
141
+ "rimraf": "4.4.1",
136
142
  "stylelint": "14.16.1",<% if (options.jsCompiler === 'ts') { %>
137
143
  "typescript": "4.9.5",<% } %>
138
144
  "webpack-cli": "4.10.0",
@@ -10,9 +10,6 @@ const baseConfig = require('@nitro/app/app/core/config');
10
10
  const defaultConfig = {
11
11
  code: {
12
12
  validation: {
13
- eslint: {
14
- live: false,
15
- },
16
13
  htmllint: {
17
14
  // enabling this live validation slows down rendering
18
15
  live: false,
@@ -22,9 +19,6 @@ const defaultConfig = {
22
19
  logMissingSchemaAsError: false,
23
20
  logMissingSchemaAsWarning: true,
24
21
  },
25
- stylelint: {
26
- live: false,
27
- },
28
22
  },
29
23
  },
30
24
  nitro: {
@@ -4,12 +4,9 @@ const theme = process.env.THEME ? process.env.THEME : validThemes.find((theme) =
4
4
  const options = {
5
5
  rules: {
6
6
  <% if (options.jsCompiler === 'ts') { %>js: false,
7
- ts: true,<% } else { %>js: {
8
- eslint: config.get('code.validation.eslint.live'),
9
- },
7
+ ts: true,<% } else { %>js: true,
10
8
  ts: false,<% } %>
11
- scss: {
12
- stylelint: config.get('code.validation.stylelint.live'),<% if (options.themes) { %>
9
+ scss: {<% if (options.themes) { %>
13
10
  implementation: require('node-sass'),<% } %>
14
11
  },
15
12
  hbs: <% if (options.clientTpl) { %>true<% } else { %>false<% } %>,
@@ -12,14 +12,6 @@ the main nodes from nitro: `code`, `nitro`,`server`, `gulp`, `feature`, `exporte
12
12
 
13
13
  ### Validation
14
14
 
15
- #### `code.validation.eslint`
16
-
17
- Type: Object
18
-
19
- - `code.validation.eslint.live` - default: false
20
-
21
- Enable/disable JavaScript linting on change.
22
-
23
15
  #### `code.validation.htmllint`
24
16
 
25
17
  Type: Object
@@ -37,14 +29,6 @@ Type: Object
37
29
  - `code.validation.jsonSchema.logMissingSchemaAsError` - default: false
38
30
  - `code.validation.jsonSchema.logMissingSchemaAsWarning` - default: true
39
31
 
40
- #### `code.validation.stylelint`
41
-
42
- Type: Object
43
-
44
- - `code.validation.stylelint.live` - default: false
45
-
46
- Enable/disable CSS linting on change.
47
-
48
32
  ## Nitro
49
33
 
50
34
  The node `nitro` contains following properties
@@ -12,7 +12,7 @@ Nitro is simple, fast and flexible. Use this app for all your frontend work.
12
12
  - Webpack Builder with HMR
13
13
  - Gulp Tasks for additional functionality
14
14
  - Linting, Source Maps, PostCSS & Browsersync
15
- - Setup for e2e and visual regression testing (cypress, backstopjs) & lighthouse
15
+ - Setup for e2e and visual regression testing (playwright, cypress, backstopjs) & lighthouse
16
16
  - Pattern generator<% if (options.clientTpl) { %>
17
17
  - [Client side templates](./client-templates.md)<% } %><% if (options.exporter) { %>
18
18
  - [Static Exports](./nitro-exporter.md)<% } %>
@@ -0,0 +1,43 @@
1
+ module.exports = async (page, scenario) => {
2
+ const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector;
3
+ const clickSelector = scenario.clickSelectors || scenario.clickSelector;
4
+ const keyPressSelector = scenario.keyPressSelectors || scenario.keyPressSelector;
5
+ const scrollToSelector = scenario.scrollToSelector;
6
+ const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int]
7
+
8
+ if (keyPressSelector) {
9
+ for (const keyPressSelectorItem of [].concat(keyPressSelector)) {
10
+ await page.waitForSelector(keyPressSelectorItem.selector);
11
+ await page.type(keyPressSelectorItem.selector, keyPressSelectorItem.keyPress);
12
+ }
13
+ }
14
+
15
+ if (hoverSelector) {
16
+ for (const hoverSelectorIndex of [].concat(hoverSelector)) {
17
+ await page.waitForSelector(hoverSelectorIndex);
18
+ await page.hover(hoverSelectorIndex);
19
+ }
20
+ }
21
+
22
+ if (clickSelector) {
23
+ for (const clickSelectorIndex of [].concat(clickSelector)) {
24
+ await page.waitForSelector(clickSelectorIndex);
25
+ await page.click(clickSelectorIndex);
26
+ }
27
+ }
28
+
29
+ if (postInteractionWait) {
30
+ if (parseInt(postInteractionWait) > 0) {
31
+ await page.waitForTimeout(postInteractionWait);
32
+ } else {
33
+ await page.waitForSelector(postInteractionWait);
34
+ }
35
+ }
36
+
37
+ if (scrollToSelector) {
38
+ await page.waitForSelector(scrollToSelector);
39
+ await page.evaluate(scrollToSelector => {
40
+ document.querySelector(scrollToSelector).scrollIntoView();
41
+ }, scrollToSelector);
42
+ }
43
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * INTERCEPT IMAGES
3
+ * Listen to all requests. If a request matches IMAGE_URL_RE
4
+ * then stub the image with data from IMAGE_STUB_URL
5
+ *
6
+ * Use this in an onBefore script E.G.
7
+ ```
8
+ module.exports = async function(page, scenario) {
9
+ require('./interceptImages')(page, scenario);
10
+ }
11
+ ```
12
+ *
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const IMAGE_URL_RE = /\.gif|\.jpg|\.png/i;
19
+ const IMAGE_STUB_URL = path.resolve(__dirname, '../../imageStub.jpg');
20
+ const IMAGE_DATA_BUFFER = fs.readFileSync(IMAGE_STUB_URL);
21
+ const HEADERS_STUB = {};
22
+
23
+ module.exports = async function (page, scenario) {
24
+ page.route(IMAGE_URL_RE, route => {
25
+ route.fulfill({
26
+ body: IMAGE_DATA_BUFFER,
27
+ headers: HEADERS_STUB,
28
+ status: 200
29
+ });
30
+ });
31
+ };
@@ -0,0 +1,16 @@
1
+ const fs = require('fs');
2
+
3
+ module.exports = async (browserContext, scenario) => {
4
+ let cookies = [];
5
+ const cookiePath = scenario.cookiePath;
6
+
7
+ // Read Cookies from File, if exists
8
+ if (fs.existsSync(cookiePath)) {
9
+ cookies = JSON.parse(fs.readFileSync(cookiePath));
10
+ }
11
+
12
+ // Add cookies to browser
13
+ browserContext.addCookies(cookies);
14
+
15
+ console.log('Cookie state restored with:', JSON.stringify(cookies, null, 2));
16
+ };
@@ -0,0 +1,3 @@
1
+ module.exports = async (page, scenario, viewport, isReference, browserContext) => {
2
+ await require('./loadCookies')(browserContext, scenario);
3
+ };
@@ -0,0 +1,6 @@
1
+ module.exports = async (page, scenario, viewport, isReference, browserContext) => {
2
+ console.log('SCENARIO > ' + scenario.label);
3
+ await require('./clickAndHoverHelper')(page, scenario);
4
+
5
+ // add more ready handlers here...
6
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * OVERRIDE CSS
3
+ * Apply this CSS to the loaded page, as a way to override styles.
4
+ *
5
+ * Use this in an onReady script E.G.
6
+ ```
7
+ module.exports = async function(page, scenario) {
8
+ await require('./overrideCSS')(page, scenario);
9
+ }
10
+ ```
11
+ *
12
+ */
13
+
14
+ const BACKSTOP_TEST_CSS_OVERRIDE = `
15
+ html {
16
+ background-image: none;
17
+ }
18
+ `;
19
+
20
+ module.exports = async (page, scenario) => {
21
+ // inject arbitrary css to override styles
22
+ await page.addStyleTag({
23
+ content: BACKSTOP_TEST_CSS_OVERRIDE
24
+ });
25
+
26
+ console.log('BACKSTOP_TEST_CSS_OVERRIDE injected for: ' + scenario.label);
27
+ };
@@ -1,39 +1,39 @@
1
- module.exports = async (page, scenario) => {
2
- const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector;
3
- const clickSelector = scenario.clickSelectors || scenario.clickSelector;
4
- const keyPressSelector = scenario.keyPressSelectors || scenario.keyPressSelector;
5
- const scrollToSelector = scenario.scrollToSelector;
6
- const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int]
7
-
8
- if (keyPressSelector) {
9
- for (const keyPressSelectorItem of [].concat(keyPressSelector)) {
10
- await page.waitFor(keyPressSelectorItem.selector);
11
- await page.type(keyPressSelectorItem.selector, keyPressSelectorItem.keyPress);
12
- }
13
- }
14
-
15
- if (hoverSelector) {
16
- for (const hoverSelectorIndex of [].concat(hoverSelector)) {
17
- await page.waitFor(hoverSelectorIndex);
18
- await page.hover(hoverSelectorIndex);
19
- }
20
- }
21
-
22
- if (clickSelector) {
23
- for (const clickSelectorIndex of [].concat(clickSelector)) {
24
- await page.waitFor(clickSelectorIndex);
25
- await page.click(clickSelectorIndex);
26
- }
27
- }
28
-
29
- if (postInteractionWait) {
30
- await page.waitFor(postInteractionWait);
31
- }
32
-
33
- if (scrollToSelector) {
34
- await page.waitFor(scrollToSelector);
35
- await page.evaluate((scrollToSelector) => {
36
- document.querySelector(scrollToSelector).scrollIntoView();
37
- }, scrollToSelector);
38
- }
39
- };
1
+ module.exports = async (page, scenario) => {
2
+ const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector;
3
+ const clickSelector = scenario.clickSelectors || scenario.clickSelector;
4
+ const keyPressSelector = scenario.keyPressSelectors || scenario.keyPressSelector;
5
+ const scrollToSelector = scenario.scrollToSelector;
6
+ const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int]
7
+
8
+ if (keyPressSelector) {
9
+ for (const keyPressSelectorItem of [].concat(keyPressSelector)) {
10
+ await page.waitForSelector(keyPressSelectorItem.selector);
11
+ await page.type(keyPressSelectorItem.selector, keyPressSelectorItem.keyPress);
12
+ }
13
+ }
14
+
15
+ if (hoverSelector) {
16
+ for (const hoverSelectorIndex of [].concat(hoverSelector)) {
17
+ await page.waitForSelector(hoverSelectorIndex);
18
+ await page.hover(hoverSelectorIndex);
19
+ }
20
+ }
21
+
22
+ if (clickSelector) {
23
+ for (const clickSelectorIndex of [].concat(clickSelector)) {
24
+ await page.waitForSelector(clickSelectorIndex);
25
+ await page.click(clickSelectorIndex);
26
+ }
27
+ }
28
+
29
+ if (postInteractionWait) {
30
+ await page.waitForTimeout(postInteractionWait);
31
+ }
32
+
33
+ if (scrollToSelector) {
34
+ await page.waitForSelector(scrollToSelector);
35
+ await page.evaluate((scrollToSelector) => {
36
+ document.querySelector(scrollToSelector).scrollIntoView();
37
+ }, scrollToSelector);
38
+ }
39
+ };
@@ -36,7 +36,7 @@ module.exports = async function (page, scenario) {
36
36
  const cookies = cookiesList.map((cookie) => `${cookie.name}=${cookie.value}`).join('; ');
37
37
  const headers = Object.assign(request.headers(), { cookie: cookies });
38
38
  const options = {
39
- headers: headers,
39
+ headers,
40
40
  body: request.postData(),
41
41
  method: request.method(),
42
42
  follow: 20,
@@ -1,31 +1,34 @@
1
1
  /// <reference types="cypress" />
2
2
 
3
+ const page404Url = '/404';
4
+ const nonExistingUrl = '/pagenotfound';
5
+
3
6
  // test 404 page
4
7
  context('404 Page Test', () => {
5
8
  beforeEach(() => {
6
- cy.visit('/404');
9
+ cy.visit(page404Url);
7
10
  });
8
11
 
9
12
  describe('HTML Head', () => {
10
- it('charset meta tag is UTF-8', () => {
13
+ it('Character encoding is UTF-8', () => {
11
14
  cy.document().should('have.property', 'charset').and('eq', 'UTF-8');
12
15
  });
13
16
 
14
- it('title includes 404', () => {
17
+ it('Title includes 404', () => {
15
18
  cy.title().should('include', '404');
16
19
  });
17
20
  });
18
21
 
19
22
  describe('Root DOM node', () => {
20
- it('has lang attribute', () => {
23
+ it('Has lang attribute', () => {
21
24
  cy.root().should('match', 'html').and('have.attr', 'lang', 'en');
22
25
  });
23
26
  });
24
27
 
25
28
  describe('Status Code', () => {
26
- it('should be 404 for a non existing page', () => {
29
+ it('Should be 404 for a non existing page', () => {
27
30
  cy.request({
28
- url: '/pagenotfound',
31
+ url: nonExistingUrl,
29
32
  failOnStatusCode: false,
30
33
  }).then((response) => {
31
34
  expect(response.status).to.eq(404);
@@ -7,11 +7,11 @@ context('Index Page Test', () => {
7
7
  });
8
8
 
9
9
  describe('Location', () => {
10
- it('has no hash', () => {
10
+ it('Has no hash', () => {
11
11
  cy.hash().should('be.empty');
12
12
  });
13
13
 
14
- it('passes window.location tests', () => {
14
+ it('Passes window.location tests', () => {
15
15
  const baseUrl = Cypress.config().baseUrl;
16
16
 
17
17
  cy.location().should((location) => {
@@ -25,17 +25,17 @@ context('Index Page Test', () => {
25
25
  });
26
26
 
27
27
  describe('HTML Head', () => {
28
- it('charset meta tag is UTF-8', () => {
28
+ it('Character encoding is UTF-8', () => {
29
29
  cy.document().should('have.property', 'charset').and('eq', 'UTF-8');
30
30
  });
31
31
 
32
- it('title includes index page', () => {
32
+ it('Title includes index page', () => {
33
33
  cy.title().should('include', 'index page');
34
34
  });
35
35
  });
36
36
 
37
37
  describe('Root DOM node', () => {
38
- it('has lang attribute', () => {
38
+ it('Has correct lang attribute', () => {
39
39
  cy.root().should('match', 'html').and('have.attr', 'lang', 'en');
40
40
  });
41
41
  });
@@ -1,6 +1,6 @@
1
1
  # Cypress e2e Tests
2
2
 
3
- End to end testing with cypress.
3
+ End-to-end testing with cypress.
4
4
 
5
5
  [More about cypress](https://www.npmjs.com/package/cypress)
6
6
 
@@ -16,7 +16,7 @@ We use the [default configuration](https://docs.cypress.io/guides/core-concepts/
16
16
  Use following npm scripts for your test workflow:
17
17
 
18
18
  - `npm run cypress-test` to run full cypress test suite (use this to set up your tests)
19
- - `npm run test:cypress` to run cypress tests in headless mode (use this for ci)
19
+ - `npm test` cypress tests are run in the test chain
20
20
 
21
21
  ## CI setup
22
22
 
@@ -0,0 +1,41 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ const page404Url = '/404';
4
+ const nonExistingUrl = '/pagenotfound';
5
+
6
+ test.describe('404 Page', () => {
7
+ test.beforeEach(async ({ page }) => {
8
+ await page.goto(page404Url);
9
+ });
10
+
11
+ test.describe('HTML Head', () => {
12
+ test('character encoding is UTF-8', async ({ page }) => {
13
+ const charset = await page.evaluate(() => window.document.characterSet);
14
+ expect(charset).toEqual('UTF-8');
15
+ });
16
+
17
+ test('Title includes 404', async ({ page }) => {
18
+ await expect(page).toHaveTitle(/404/);
19
+ });
20
+ });
21
+
22
+ test.describe('Root DOM node', () => {
23
+ test('Has correct lang attribute', async ({ page }) => {
24
+ const lang = await page.locator('html').getAttribute('lang');
25
+ expect(lang).toEqual('en');
26
+ });
27
+ });
28
+ });
29
+
30
+ test.describe('404 Page', () => {
31
+ test.describe('Status Code', () => {
32
+ test('Should be 404 for a non existing page', async ({ page }) => {
33
+ page.on('response', (response) => {
34
+ if (response.url().includes(nonExistingUrl)) {
35
+ expect(response.status()).toBe(404);
36
+ }
37
+ });
38
+ await page.goto(nonExistingUrl);
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,42 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test.describe('Index Page', () => {
4
+ test.beforeEach(async ({ page }) => {
5
+ await page.goto('/index');
6
+ });
7
+
8
+ test.describe('Location', () => {
9
+ test('Has no hash', async ({ page }) => {
10
+ expect(page.url().includes('#')).toBeFalsy();
11
+ });
12
+
13
+ test('Passes window.location tests', async ({ page }, testInfo) => {
14
+ const baseUrl = testInfo.project.use.baseURL;
15
+ const location = await page.evaluate(() => window.location);
16
+
17
+ expect(location.hash).toEqual('');
18
+ expect(location.href).toEqual(`${baseUrl}/index`);
19
+ expect(location.origin).toEqual(`${baseUrl}`);
20
+ expect(location.pathname).toEqual('/index');
21
+ expect(location.search).toEqual('');
22
+ });
23
+ });
24
+
25
+ test.describe('HTML Head', () => {
26
+ test('Character encoding is UTF-8', async ({ page }) => {
27
+ const charset = await page.evaluate(() => window.document.characterSet);
28
+ expect(charset).toEqual('UTF-8');
29
+ });
30
+
31
+ test('Title includes index page', async ({ page }) => {
32
+ await expect(page).toHaveTitle(/index page/);
33
+ });
34
+ });
35
+
36
+ test.describe('Root DOM node', () => {
37
+ test('Has correct lang attribute', async ({ page }) => {
38
+ const lang = await page.locator('html').getAttribute('lang');
39
+ expect(lang).toEqual('en');
40
+ });
41
+ });
42
+ });
@@ -0,0 +1,108 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+
3
+ const port = process.env.PORT || 8080;
4
+
5
+ /**
6
+ * See https://playwright.dev/docs/test-configuration
7
+ */
8
+ export default defineConfig({
9
+ testDir: './e2e',
10
+ /* Maximum time one test can run for. */
11
+ timeout: 30 * 1000,
12
+ expect: {
13
+ /**
14
+ * Maximum time expect() should wait for the condition to be met.
15
+ * For example in `await expect(locator).toHaveText();`
16
+ */
17
+ timeout: 5000,
18
+ },
19
+ /* Run tests in files in parallel */
20
+ fullyParallel: true,
21
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
22
+ forbidOnly: !!process.env.CI,
23
+ /* Retry on CI only */
24
+ retries: process.env.CI ? 2 : 0,
25
+ /* Opt out of parallel tests on CI. */
26
+ workers: process.env.CI ? 1 : undefined,
27
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
28
+ reporter: process.env.CI
29
+ ? [
30
+ ['dot'],
31
+ [
32
+ 'html',
33
+ {
34
+ open: 'never',
35
+ outputFolder: '../../public/reports/playwright',
36
+ },
37
+ ],
38
+ ]
39
+ : [
40
+ ['list'],
41
+ [
42
+ 'html',
43
+ {
44
+ open: 'on-failure',
45
+ outputFolder: '../../public/reports/playwright',
46
+ },
47
+ ],
48
+ ],
49
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
50
+ use: {
51
+ /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
52
+ actionTimeout: 0,
53
+ /* Base URL to use in actions like `await page.goto('/')`. */
54
+ baseURL: `http://localhost:${port}`,
55
+ /* Capture screenshot. Defaults to 'off' */
56
+ screenshot: 'only-on-failure',
57
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
58
+ trace: 'on-first-retry',
59
+ },
60
+
61
+ /* Configure projects for major browsers */
62
+ projects: [
63
+ {
64
+ name: 'chromium',
65
+ use: { ...devices['Desktop Chrome'] },
66
+ },
67
+
68
+ // {
69
+ // name: 'firefox',
70
+ // use: { ...devices['Desktop Firefox'] },
71
+ // },
72
+
73
+ // {
74
+ // name: 'webkit',
75
+ // use: { ...devices['Desktop Safari'] },
76
+ // },
77
+
78
+ /* Test against mobile viewports. */
79
+ // {
80
+ // name: 'Mobile Chrome',
81
+ // use: { ...devices['Pixel 5'] },
82
+ // },
83
+ // {
84
+ // name: 'Mobile Safari',
85
+ // use: { ...devices['iPhone 12'] },
86
+ // },
87
+
88
+ /* Test against branded browsers. */
89
+ // {
90
+ // name: 'Microsoft Edge',
91
+ // use: { channel: 'msedge' },
92
+ // },
93
+ // {
94
+ // name: 'Google Chrome',
95
+ // use: { channel: 'chrome' },
96
+ // },
97
+ ],
98
+
99
+ /* Folder for test artifacts such as screenshots, videos, traces, etc. */
100
+ outputDir: 'test-results/',
101
+
102
+ /* Run your local dev server before starting the tests */
103
+ webServer: {
104
+ command: 'npm run prod:serve',
105
+ port: Number(port),
106
+ reuseExistingServer: true,
107
+ },
108
+ });
@@ -0,0 +1,25 @@
1
+ # Playwright e2e Tests
2
+
3
+ End-to-end testing with playwright.
4
+
5
+ [More about playwright](https://playwright.dev/)
6
+
7
+ ## Directories
8
+
9
+ - `./e2e` is the place for your tests (you may create subfolders as well)
10
+
11
+ ## Scripts
12
+
13
+ Use following npm scripts for your test workflow:
14
+
15
+ - `npm test` playwright tests run in the test chain (this also works well in ci environments)
16
+ - `npm run playwright-test` to run all tests on your local machine
17
+ - `npm run playwright-test-generate` to open the playwright generator (use this to set up your tests)
18
+
19
+ or [run and generate playwright tests directly in VS Code](https://playwright.dev/docs/codegen#generate-tests-in-vs-code)
20
+
21
+ ## CI setup
22
+
23
+ Add `CI=true` as environment variable.
24
+
25
+ More on [configuation for continous integration](https://playwright.dev/docs/ci)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-nitro",
3
- "version": "7.3.0",
3
+ "version": "7.4.0",
4
4
  "description": "Yeoman generator for the nitro frontend framework",
5
5
  "license": "MIT",
6
6
  "repository": "merkle-open/generator-nitro",
@@ -45,11 +45,11 @@
45
45
  },
46
46
  "devDependencies": {
47
47
  "@merkle-open/eslint-config": "1.0.0",
48
- "ejs": "3.1.8",
48
+ "ejs": "3.1.9",
49
49
  "eslint": "7.32.0",
50
50
  "eslint-plugin-import": "2.27.5",
51
- "fs-extra": "11.1.0",
52
- "jasmine": "4.5.0",
51
+ "fs-extra": "11.1.1",
52
+ "jasmine": "4.6.0",
53
53
  "yeoman-assert": "3.1.1",
54
54
  "yeoman-test": "6.3.0"
55
55
  }