Isolate e2e, integration, and unit test environments

- Setup projects for e2e, integration, and unit tests in Jest configuration
- Setup /tests/e2e/ tests for Jest+PlayWright
- Setup /tests/integration/ tests for Jest
- Setup /tests/unit/ tests for Jest
- Setup eslint for Jest and Jest+Playwright environments
- Move e2e configuration files into separate folder
- Update e2e example tests
- Update unit example tests
- Update HTML fixtures
This commit is contained in:
John Hildenbiddle
2020-07-13 18:26:45 -04:00
parent e8da21d3dc
commit aed09be687
19 changed files with 423 additions and 165 deletions
+24 -3
View File
@@ -1,5 +1,26 @@
// Jest-Playwrigth Config
process.env.JEST_PLAYWRIGHT_CONFIG =
'./tests/e2e/config/jest-playwright.config.js';
module.exports = {
globalSetup: './tests/jest.setup.js',
globalTeardown: './tests/jest.teardown.js',
preset: 'jest-playwright-preset',
projects: [
// Unit Tests (Jest)
{
displayName: 'unit',
testMatch: ['<rootDir>/tests/unit/*.test.js'],
},
// Integration Tests (Jest)
{
displayName: 'integration',
testMatch: ['<rootDir>/tests/integration/*.test.js'],
},
// E2E Tests (Jest + Playwright)
{
displayName: 'e2e',
globalSetup: './tests/e2e/config/jest.setup.js',
globalTeardown: './tests/e2e/config/jest.teardown.js',
preset: 'jest-playwright-preset',
testMatch: ['<rootDir>/tests/e2e/*.test.js'],
},
],
};
+94
View File
@@ -3191,6 +3191,12 @@
"@types/istanbul-lib-report": "*"
}
},
"@types/json-schema": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -3279,6 +3285,76 @@
"@types/node": "*"
}
},
"@typescript-eslint/experimental-utils": {
"version": "2.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz",
"integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/typescript-estree": "2.34.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0"
},
"dependencies": {
"eslint-scope": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
"integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
"estraverse": "^4.1.1"
}
},
"eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
}
}
},
"@typescript-eslint/typescript-estree": {
"version": "2.34.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz",
"integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==",
"dev": true,
"requires": {
"debug": "^4.1.1",
"eslint-visitor-keys": "^1.1.0",
"glob": "^7.1.6",
"is-glob": "^4.0.1",
"lodash": "^4.17.15",
"semver": "^7.3.2",
"tsutils": "^3.17.1"
},
"dependencies": {
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"dev": true
}
}
},
"@zkochan/cmd-shim": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@zkochan/cmd-shim/-/cmd-shim-3.1.0.tgz",
@@ -7678,6 +7754,15 @@
}
}
},
"eslint-plugin-jest": {
"version": "23.18.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.18.0.tgz",
"integrity": "sha512-wLPM/Rm1SGhxrFQ2TKM/BYsYPhn7ch6ZEK92S2o/vGkAAnDXM0I4nTIo745RIX+VlCRMFgBuJEax6XfTHMdeKg==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "^2.5.0"
}
},
"eslint-plugin-jest-playwright": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest-playwright/-/eslint-plugin-jest-playwright-0.2.1.tgz",
@@ -19369,6 +19454,15 @@
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
"dev": true
},
"tsutils": {
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
"integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+5 -2
View File
@@ -31,8 +31,10 @@
"test": "mocha ./test/**/*.test.js",
"testServer": "node cypress/setup.js",
"test:e2e": "start-server-and-test testServer http://localhost:3000 cy:run",
"test:jest": "jest ./tests/**/*.test.js",
"test:jest-example": "jest ./tests/example.test.js",
"test:jest": "jest",
"test:jest-e2e": "jest --selectProjects e2e",
"test:jest-integration": "jest --selectProjects integration",
"test:jest-unit": "jest --selectProjects unit",
"posttest:e2e": "rimraf cypress/fixtures/docs",
"cy:run": "cypress run",
"cy:open": "cypress open ",
@@ -82,6 +84,7 @@
"cypress-image-snapshot": "^3.1.1",
"eslint": "^5.16.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jest": "^23.18.0",
"eslint-plugin-jest-playwright": "^0.2.1",
"eslint-plugin-prettier": "^3.1.2",
"esm": "^3.1.4",
+5 -1
View File
@@ -1,3 +1,7 @@
module.exports = {
extends: ['plugin:jest-playwright/recommended'],
env: {
'jest/globals': true,
},
plugins: ['jest'],
extends: ['plugin:jest/recommended', 'plugin:jest/style'],
};
+16 -10
View File
@@ -1,20 +1,26 @@
# Docsify Tests
# Docsify Testing
## Jest
## Environment
A test framework used for assertions, mocking, spies, etc.
- Tests located in `/tests/e2e/` use [Jest](https://jestjs.io) and [Playwright](https://playwright.dev)
- Tests located in `/tests/integration/` use [Jest](https://jestjs.io)
- Tests located in `/tests/unit/` use [Jest](https://jestjs.io)
### Jest
A test framework used for assertions, mocks, spies, etc.
- Docs: https://jestjs.io
## Jest-Playwright
### Playwright
A test automation tool for launching browsers and manipulating the DOM.
- Docs: https://playwright.dev
### Jest-Playwright
A Jest preset that simplifies using Jest and Playwright together.
- Docs: https://github.com/playwright-community/jest-playwright
- Info: https://playwright.tech/blog/using-jest-with-playwright
## Playwright
A test automation tool for launching browsers and manipulating the DOM
- Docs: https://playwright.dev
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: ['plugin:jest-playwright/recommended'],
};
@@ -1,6 +1,6 @@
const path = require('path');
const liveServer = require('live-server');
const { host, port } = require('./helpers/server.js');
const { host, port } = require('./server.js');
module.exports = async () => {
global.__SERVER__ = liveServer; // Instance reference for teardown script
@@ -8,12 +8,12 @@ module.exports = async () => {
liveServer.start({
host,
port,
root: path.resolve(__dirname, 'fixtures'),
root: path.resolve(__dirname, '../fixtures'),
open: false,
logLevel: 0,
mount: [
['/docs', path.resolve(__dirname, '../docs')],
['/lib', path.resolve(__dirname, '../lib')],
['/docs', path.resolve(__dirname, '../../../docs')],
['/lib', path.resolve(__dirname, '../../../lib')],
],
});
};
@@ -1,5 +1,5 @@
module.exports = {
host: 'localhost',
host: '127.0.0.1',
port: 8080,
get URL() {
return `http://${this.host}:${this.port}`;
+219
View File
@@ -0,0 +1,219 @@
// Modules, constants, and variables
// -----------------------------------------------------------------------------
const add = require('./fixtures/add.js');
const docsifyInit = require('./helpers/docsifyInit');
const { URL: serverURL } = require('./config/server.js');
// Suite
// -----------------------------------------------------------------------------
describe(`Example Tests`, function() {
// Setup & Teardown
// ---------------------------------------------------------------------------
beforeEach(async () => {
// Goto URL (fixtures/index.html)
// https://playwright.dev/#path=docs%2Fapi.md&q=pagegotourl-options
// NOTE: Tests typically begin by navigating to a page for testing. When
// this doesn't happen, Playwright operates on the "about:blank" page which
// will cause operations that require the window location to be a valid URL
// to fail (e.g. AJAX requests). To avoid these issues, this hook ensures
// that each tests begins by loading the default test server page.
await page.goto(serverURL);
});
// Tests
// ---------------------------------------------------------------------------
test('dom manipulation', async () => {
const testText = 'This is a test';
const testHTML = `<h1>Test</h1><p>${testText}</p>`;
// Inject HTML
// https://playwright.dev/#path=docs%2Fapi.md&q=pagesetcontenthtml-options
await page.setContent(testHTML);
// Add class to <body> element and verify
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevalselector-pagefunction-arg
await page.$eval('body', elm => elm.classList.add('foo'));
expect(await page.getAttribute('body', 'class')).toEqual('foo');
// Test using helper methods from expect-playright (via jest-playwright)
// https://github.com/playwright-community/expect-playwright
// https://playwright.tech/blog/using-jest-with-playwright
await expect(page).toHaveText('body', 'Test');
await expect(page).toHaveSelector('p');
await expect(page).toEqualText('p', testText);
await expect(page).not.toHaveSelector('table', { timeout: 1 });
// Test using standard jest + playwrite methods
// https://playwright.dev/#path=docs%2Fapi.md&q=pagetextcontentselector-options
expect(await page.textContent('body')).toMatch(/Test/);
await page.waitForSelector('p');
expect(await page.textContent('p')).toEqual(testText);
await page.waitForSelector('table', { state: 'detached' });
});
test('javascript in the browser context', async () => {
// Get native DOM values
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
const clientDimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
};
});
// Get result of script executed in browser context
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
const scriptResult = await page.evaluate(
numbers => {
const result = numbers.reduce(
(accumulator, currentValue) => accumulator + currentValue
);
return Promise.resolve(result);
},
[1, 2, 3]
);
// Get result of local function executed in browser context
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
const functionResult = await page.evaluate(`
${add.toString()}
const result = add(1, 2, 3);
Promise.resolve(result);
`);
expect(clientDimensions).toHaveProperty('width');
expect(typeof clientDimensions.width).toBe('number');
expect(clientDimensions).toHaveProperty('height');
expect(typeof clientDimensions.height).toBe('number');
expect(scriptResult).toBe(6);
expect(functionResult).toBe(6);
});
test('docsify test using playwright methods', async () => {
// Goto URL
// https://playwright.dev/#path=docs%2Fapi.md&q=pagegotourl-options
await page.goto(`${serverURL}/html5.html`);
// Set docsify configuration
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
await page.evaluate(() => {
window.$docsify = {
el: '#app',
basePath: '/docs',
themeColor: 'red',
};
});
// Add docsify target element
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevalselector-pagefunction-arg
await page.$eval('body', elm => {
elm.innerHTML = '<div id="app"></div>';
});
// Inject docsify theme (vue.css)
// https://playwright.dev/#path=docs%2Fapi.md&q=pageaddstyletagoptions
await page.addStyleTag({ url: `${serverURL}/lib/themes/vue.css` });
// Inject docsify.js
// https://playwright.dev/#path=docs%2Fapi.md&q=pageaddscripttagoptions
await page.addScriptTag({ url: `${serverURL}/lib/docsify.js` });
// Wait for docsify to initialize
// https://playwright.dev/#path=docs%2Fapi.md&q=pagewaitforselectorselector-options
await page.waitForSelector('#main');
// Create handle for JavaScript object in browser
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
const $docsify = await page.evaluate(() => window.$docsify);
// Test object property and value
expect($docsify).toHaveProperty('themeColor', 'red');
});
//
test('docsify test using docsifyInit()', async () => {
// Load custom docsify
// (See ./helpers/docsifyInit.js for details)
await docsifyInit(page, {
config: {
themeColor: 'red',
},
});
// Create handle for JavaScript object in browser
// https://playwright.dev/#path=docs%2Fapi.md&q=pageevaluatepagefunction-arg
const $docsify = await page.evaluate(() => window.$docsify);
// Test object property and value
expect($docsify).toHaveProperty('themeColor', 'red');
});
test('custom docsify site using docsifyInit()', async () => {
// Load custom docsify
// (See ./helpers/docsifyInit.js for details)
await docsifyInit(page, {
config: {
name: 'Docsify Test',
},
content: `
# Page Title
This is a paragraph
1. Item
1. Item
1. Item
`,
coverpage: `
# Docsify Test
> Testing a magical documentation site generator
[GitHub](https://github.com/docsifyjs/docsify/)
[Getting Started](#page-title)
`,
navbar: `
- [docsify.js.org](https://docsify.js.org/#/)
`,
sidebar: `
- [Test Page](test)
`,
routes: [
[
'**/test.md',
`
# Test Page
This is a custom route
`,
],
],
script: `
console.log('Injected JavaScript');
`,
scriptURLs: ['https://cdn.jsdelivr.net/npm/docsify-themeable@0'],
style: `
:root {
--theme-hue: 275;
}
`,
styleURLs: [
'https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css',
],
});
// Click the test page link
// https://playwright.dev/#path=docs%2Fapi.md&q=pageclickselector-options
await page.click('a[href="#/test"]');
// Verify page change by checking URL
await expect(page.url()).toMatch(/\/test$/);
// Or verify page change by checking page content
await expect(page).toHaveText('#main', 'This is a custom route');
});
});
+7
View File
@@ -0,0 +1,7 @@
function add(...addends) {
return addends.reduce(
(accumulator, currentValue) => accumulator + currentValue
);
}
module.exports = add;
@@ -1,5 +1,3 @@
<!-- TEMPLATE FOR JEST+PLAYWRIGHT -->
<!DOCTYPE html>
<html>
<head>
@@ -8,7 +6,6 @@
<meta charset="UTF-8" />
<title>docsify</title>
<script>
// Default configuration (modified via docsifyInit)
window.$docsify = {
el: '#app',
basePath: '/docs',
+7
View File
@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body></body>
</html>
View File
@@ -1,5 +1,5 @@
const stripIndent = require('common-tags/lib/stripIndent');
const { URL: serverURL } = require('./server.js');
const { URL: serverURL } = require('../config/server.js');
/**
* Playwright helper for dynamically creating custom docsify sites.
@@ -16,7 +16,7 @@ const { URL: serverURL } = require('./server.js');
* @param {string|String[]} [options.styleURLs=['/lib/themes/vue.css']] External CSS to inject via <link rel="stylesheet"> tag(s)
* @param {String} [options.script] JS to inject via <script> tag
* @param {string|String[]} [options.scriptURLs] External JS to inject via <script src="..."> tag(s)
* @param {String} [options.url=serverURL] URL of local test server
* @param {String} [options.url=`${serverURL}/docsify-init.html`] URL of local test server
* @param {String} [options.docsifyURL='/lib/docsify.js']
* @param {String} [options.waitForSelector='#main']
*/
@@ -32,7 +32,7 @@ async function docsifyInit(page, options) {
styleURLs: ['/lib/themes/vue.css'],
script: '',
scriptURLs: [],
url: serverURL,
url: `${serverURL}/docsify-init.html`,
docsifyURL: '/lib/docsify.js',
waitForSelector: '#main',
};
+1 -7
View File
@@ -1,15 +1,9 @@
const docsifyInit = require('../helpers/docsifyInit');
const docsifyInit = require('./helpers/docsifyInit');
describe(`Vue.js Rendering`, function() {
const content = `<div id="test">test<span v-for="i in 5">{{ i }}</span></div>`;
const scriptURLs = ['https://unpkg.com/vue@2/dist/vue.js'];
// Setup & Teardown
// -------------------------------------------------------------------------
beforeEach(async () => {
await jestPlaywright.resetPage();
});
// Tests
// ---------------------------------------------------------------------------
test('does render Vue content when executeScript is unspecified', async () => {
-131
View File
@@ -1,131 +0,0 @@
// Imports, Modules, Constants, and Variables
// ---------------------------------------------------------------------------
const docsifyInit = require('./helpers/docsifyInit');
// Functions
// ---------------------------------------------------------------------------
function add(...numbers) {
return numbers.reduce(
(accumulator, currentValue) => accumulator + currentValue
);
}
describe(`Example Tests`, function() {
// Setup & Teardown
// ---------------------------------------------------------------------------
beforeEach(async () => {
await jestPlaywright.resetPage();
});
// Tests
// ---------------------------------------------------------------------------
test('function', async () => {
const testValue = add(1, 2, 3);
expect(testValue).toBe(6);
});
test('injected HTML', async () => {
const testText = 'This is a test';
const testHTML = `<h1>Test</h1><p>${testText}</p>`;
// Inject HTML
// https://playwright.dev/#path=docs%2Fapi.md&q=pagesetcontenthtml-options
await page.setContent(testHTML);
// Use helper methods from expect-playright (via jest-playwright)
// - https://github.com/playwright-community/expect-playwright
// - https://playwright.tech/blog/using-jest-with-playwright
await expect(page).toHaveText('body', 'Test');
await expect(page).toHaveSelector('p');
await expect(page).toEqualText('p', testText);
await expect(page).not.toHaveSelector('table', { timeout: 1 });
// Or use standard jest + playwrite methods
// https://playwright.dev/#path=docs%2Fapi.md&q=pagetextcontentselector-options
expect(await page.textContent('body')).toMatch(/Test/);
expect(await page.waitForSelector('p'));
expect(await page.textContent('p')).toEqual(testText);
expect(await page.waitForSelector('table', { state: 'detached' }));
});
test('docsify docs site', async () => {
// Load docsify docs site
// See ./helpers/docsifyInit.js for details
await docsifyInit(page, {
basePath: '/docs', // default
});
// TIP 1: Use the jestPlaywright.debug() helper to pause execution
// TIP 2: Debug with devtools by setting `headless: false` in jest-playwright.config.js
// await jestPlaywright.debug();
expect(await page.title()).toBe('docsify');
});
test('docsify custom site', async () => {
// Load a custom docsify site
// See ./helpers/docsifyInit.js for details
await docsifyInit(page, {
config: {
name: 'Docsify Test',
},
content: `
# Page Title
This is a paragraph
1. Item
1. Item
1. Item
`,
coverpage: `
# Docsify Test
> Testing a magical documentation site generator
[GitHub](https://github.com/docsifyjs/docsify/)
[Getting Started](#page-title)
`,
navbar: `
- [docsify.js.org](https://docsify.js.org/#/)
`,
sidebar: `
- [Test Page](test)
`,
routes: [
[
'**/test.md',
`
# Test Page
This is a custom route
`,
],
],
script: `
console.log('Injected JavaScript');
`,
scriptURLs: ['https://cdn.jsdelivr.net/npm/docsify-themeable@0'],
style: `
:root {
--theme-hue: 275;
}
`,
styleURLs: [
'https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css',
],
});
// await jestPlaywright.debug();
// Click the test page link
await page.click('a[href="#/test"]');
// Verify page change by checking URL
await expect(page.url()).toMatch(/\/test$/);
// Or verify page change by checking page content
await expect(page).toHaveText('#main', 'This is a custom route');
});
});
+34
View File
@@ -0,0 +1,34 @@
// Suite
// -----------------------------------------------------------------------------
describe(`Example Tests`, function() {
// Tests
// ---------------------------------------------------------------------------
test('javascript (node)', async () => {
function add(...addends) {
return addends.reduce(
(accumulator, currentValue) => accumulator + currentValue
);
}
const testValue = add(1, 2, 3);
expect(testValue).toBe(6);
});
test('dom manipulation (jsdom)', async () => {
const testText = 'This is a test';
const testHTML = `<h1>Test</h1><p>${testText}</p>`;
// Inject HTML
document.body.innerHTML = testHTML;
// Add class to <body> element and verify
document.body.classList.add('foo');
// Test HTML
expect(document.body.getAttribute('class')).toEqual('foo');
expect(document.body.textContent).toMatch(/Test/);
expect(document.querySelectorAll('p')).toHaveLength(1);
expect(document.querySelector('p').textContent).toBe(testText);
expect(document.querySelector('table')).toBeNull();
});
});