From aed09be68798995ec028bc5a561b46a2aa240b6e Mon Sep 17 00:00:00 2001 From: John Hildenbiddle Date: Mon, 13 Jul 2020 18:26:45 -0400 Subject: [PATCH] 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 --- jest.config.js | 27 ++- package-lock.json | 94 ++++++++ package.json | 7 +- tests/.eslintrc.js | 6 +- tests/README.md | 26 ++- tests/e2e/.eslintrc.js | 3 + .../e2e/config/jest-playwright.config.js | 0 tests/{ => e2e/config}/jest.setup.js | 8 +- tests/{ => e2e/config}/jest.teardown.js | 0 tests/{helpers => e2e/config}/server.js | 2 +- tests/e2e/example.test.js | 219 ++++++++++++++++++ tests/e2e/fixtures/add.js | 7 + .../fixtures/docsify-init.html} | 3 - tests/e2e/fixtures/html5.html | 7 + tests/e2e/fixtures/index.html | 0 tests/{ => e2e}/helpers/docsifyInit.js | 6 +- tests/e2e/vue.test.js | 8 +- tests/example.test.js | 131 ----------- tests/unit/example.test.js | 34 +++ 19 files changed, 423 insertions(+), 165 deletions(-) create mode 100644 tests/e2e/.eslintrc.js rename jest-playwright.config.js => tests/e2e/config/jest-playwright.config.js (100%) rename tests/{ => e2e/config}/jest.setup.js (57%) rename tests/{ => e2e/config}/jest.teardown.js (100%) rename tests/{helpers => e2e/config}/server.js (82%) create mode 100644 tests/e2e/example.test.js create mode 100644 tests/e2e/fixtures/add.js rename tests/{fixtures/index.html => e2e/fixtures/docsify-init.html} (80%) create mode 100644 tests/e2e/fixtures/html5.html create mode 100644 tests/e2e/fixtures/index.html rename tests/{ => e2e}/helpers/docsifyInit.js (95%) delete mode 100644 tests/example.test.js create mode 100644 tests/unit/example.test.js diff --git a/jest.config.js b/jest.config.js index 3b130cd6..8b94f05c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -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: ['/tests/unit/*.test.js'], + }, + // Integration Tests (Jest) + { + displayName: 'integration', + testMatch: ['/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: ['/tests/e2e/*.test.js'], + }, + ], }; diff --git a/package-lock.json b/package-lock.json index 42fe2ecb..f1b07124 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 301ff37a..3d587304 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/tests/.eslintrc.js b/tests/.eslintrc.js index 5f3fcf67..742332f1 100644 --- a/tests/.eslintrc.js +++ b/tests/.eslintrc.js @@ -1,3 +1,7 @@ module.exports = { - extends: ['plugin:jest-playwright/recommended'], + env: { + 'jest/globals': true, + }, + plugins: ['jest'], + extends: ['plugin:jest/recommended', 'plugin:jest/style'], }; diff --git a/tests/README.md b/tests/README.md index 352f4aa0..9dced143 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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 diff --git a/tests/e2e/.eslintrc.js b/tests/e2e/.eslintrc.js new file mode 100644 index 00000000..5f3fcf67 --- /dev/null +++ b/tests/e2e/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['plugin:jest-playwright/recommended'], +}; diff --git a/jest-playwright.config.js b/tests/e2e/config/jest-playwright.config.js similarity index 100% rename from jest-playwright.config.js rename to tests/e2e/config/jest-playwright.config.js diff --git a/tests/jest.setup.js b/tests/e2e/config/jest.setup.js similarity index 57% rename from tests/jest.setup.js rename to tests/e2e/config/jest.setup.js index 75727ff7..51e9bfd2 100644 --- a/tests/jest.setup.js +++ b/tests/e2e/config/jest.setup.js @@ -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')], ], }); }; diff --git a/tests/jest.teardown.js b/tests/e2e/config/jest.teardown.js similarity index 100% rename from tests/jest.teardown.js rename to tests/e2e/config/jest.teardown.js diff --git a/tests/helpers/server.js b/tests/e2e/config/server.js similarity index 82% rename from tests/helpers/server.js rename to tests/e2e/config/server.js index e75417fc..826e731c 100644 --- a/tests/helpers/server.js +++ b/tests/e2e/config/server.js @@ -1,5 +1,5 @@ module.exports = { - host: 'localhost', + host: '127.0.0.1', port: 8080, get URL() { return `http://${this.host}:${this.port}`; diff --git a/tests/e2e/example.test.js b/tests/e2e/example.test.js new file mode 100644 index 00000000..49ec6d7c --- /dev/null +++ b/tests/e2e/example.test.js @@ -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 = `

Test

${testText}

`; + + // Inject HTML + // https://playwright.dev/#path=docs%2Fapi.md&q=pagesetcontenthtml-options + await page.setContent(testHTML); + + // Add class to 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 = '
'; + }); + + // 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'); + }); +}); diff --git a/tests/e2e/fixtures/add.js b/tests/e2e/fixtures/add.js new file mode 100644 index 00000000..32323ed7 --- /dev/null +++ b/tests/e2e/fixtures/add.js @@ -0,0 +1,7 @@ +function add(...addends) { + return addends.reduce( + (accumulator, currentValue) => accumulator + currentValue + ); +} + +module.exports = add; diff --git a/tests/fixtures/index.html b/tests/e2e/fixtures/docsify-init.html similarity index 80% rename from tests/fixtures/index.html rename to tests/e2e/fixtures/docsify-init.html index 856d3d53..d6da1708 100644 --- a/tests/fixtures/index.html +++ b/tests/e2e/fixtures/docsify-init.html @@ -1,5 +1,3 @@ - - @@ -8,7 +6,6 @@ docsify