mirror of
https://github.com/docsifyjs/docsify.git
synced 2026-01-18 15:13:00 +00:00
pin playwright to 1.8 to avoid regressions, and fix flaky tests
Playwright had some in-range breaking changes (regressions): https://github.com/microsoft/playwright/issues/10819 and https://github.com/microsoft/playwright/issues/11570 Playwright tests need to `await onceRendered()` after each route navigation to wait for render to finish before testing the state of the rendered DOM. Tests were flaky because they were looking for DOM before render finished (sometimes). Additionally, this fixes one test that was failing only locally, but not in CI, due to a RegExp check against page.url() (not sure why it would differ on CI vs local, but now the URL is explicit).
This commit is contained in:
parent
9658ed4d3a
commit
38f2d243b8
4
.gitignore
vendored
4
.gitignore
vendored
@ -8,3 +8,7 @@ themes/
|
||||
|
||||
# exceptions
|
||||
!.gitkeep
|
||||
|
||||
# libraries don't need lock files, only apps do.
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
@ -118,7 +118,7 @@
|
||||
'/zh-cn/': '搜索',
|
||||
'/': 'Search',
|
||||
},
|
||||
pathNamespaces: ['/es', '/de-de', '/ru-ru', '/zh-cn']
|
||||
pathNamespaces: ['/es', '/de-de', '/ru-ru', '/zh-cn'],
|
||||
},
|
||||
vueComponents: {
|
||||
'button-counter': {
|
||||
@ -208,6 +208,31 @@
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Docsify tests rely on this plugin.
|
||||
*
|
||||
* This plugin increments a `data-render-count="N"` attribute on the
|
||||
* `<body>` element after each markdown page render. F.e.
|
||||
* `data-render-count="1"`, `data-render-count="2"`, etc.
|
||||
*
|
||||
* The very first render from calling docsifyInit() will result in a
|
||||
* value of 1. Each following render (f.e. from clicking a link) will
|
||||
* increment once rendering of the new page is finished.
|
||||
*
|
||||
* We do this so that we can easily wait for a render to finish before
|
||||
* testing the state of the render result, or else we'll have flaky
|
||||
* tests (a "race condition").
|
||||
*/
|
||||
function RenderCountPlugin(hook) {
|
||||
hook.init(() => {
|
||||
document.body.dataset.renderCount = 0;
|
||||
});
|
||||
|
||||
hook.doneEach(() => {
|
||||
document.body.dataset.renderCount++;
|
||||
});
|
||||
},
|
||||
],
|
||||
};
|
||||
</script>
|
||||
|
||||
27
index.html
27
index.html
@ -82,7 +82,7 @@
|
||||
'/zh-cn/': '搜索',
|
||||
'/': 'Search',
|
||||
},
|
||||
pathNamespaces: ['/es', '/de-de', '/ru-ru', '/zh-cn']
|
||||
pathNamespaces: ['/es', '/de-de', '/ru-ru', '/zh-cn'],
|
||||
},
|
||||
plugins: [
|
||||
DocsifyCarbon.create('CEBI6KQE', 'docsifyjsorg'),
|
||||
@ -110,6 +110,31 @@
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Docsify tests rely on this plugin.
|
||||
*
|
||||
* This plugin increments a `data-render-count="N"` attribute on the
|
||||
* `<body>` element after each markdown page render. F.e.
|
||||
* `data-render-count="1"`, `data-render-count="2"`, etc.
|
||||
*
|
||||
* The very first render from calling docsifyInit() will result in a
|
||||
* value of 1. Each following render (f.e. from clicking a link) will
|
||||
* increment once rendering of the new page is finished.
|
||||
*
|
||||
* We do this so that we can easily wait for a render to finish before
|
||||
* testing the state of the render result, or else we'll have flaky
|
||||
* tests (a "race condition").
|
||||
*/
|
||||
function RenderCountPlugin(hook) {
|
||||
hook.init(() => {
|
||||
document.body.dataset.renderCount = 0;
|
||||
});
|
||||
|
||||
hook.doneEach(() => {
|
||||
document.body.dataset.renderCount++;
|
||||
});
|
||||
},
|
||||
],
|
||||
};
|
||||
</script>
|
||||
|
||||
42125
package-lock.json
generated
42125
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -109,7 +109,7 @@
|
||||
"live-server": "^1.2.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"playwright": "^1.8.0",
|
||||
"playwright": "1.8.0",
|
||||
"prettier": "^1.19.1",
|
||||
"rimraf": "^3.0.0",
|
||||
"rollup": "^2.63.0",
|
||||
|
||||
@ -26,7 +26,7 @@ describe(`Example Tests`, function() {
|
||||
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 });
|
||||
await expect(page).not.toHaveSelector('table', { timeout: 15000 });
|
||||
|
||||
// Test using standard jest + playwrite methods
|
||||
// https://playwright.dev/#path=docs%2Fapi.md&q=pagetextcontentselector-options
|
||||
@ -78,13 +78,16 @@ describe(`Example Tests`, function() {
|
||||
);
|
||||
}
|
||||
|
||||
const functionResult = await page.evaluate(`
|
||||
// This one requires the code to be wrapped in an IIFE or else there's a
|
||||
// regression between playwright v1.8 and v1.17 that causes an error,
|
||||
// https://github.com/microsoft/playwright/issues/10819
|
||||
const functionResult = await page.evaluate(`(() => {
|
||||
${add.toString()}
|
||||
|
||||
const result = add(1, 2, 3);
|
||||
|
||||
Promise.resolve(result);
|
||||
`);
|
||||
return Promise.resolve(result);
|
||||
})()`);
|
||||
|
||||
expect(functionResult).toBe(6);
|
||||
});
|
||||
|
||||
@ -15,14 +15,15 @@ describe(`Index file hosting`, function() {
|
||||
'#main',
|
||||
'A magical documentation site generator'
|
||||
);
|
||||
expect(page.url()).toMatch(/index\.html#\/$/);
|
||||
expect(page.url()).toMatch(sharedOptions.testURL);
|
||||
});
|
||||
|
||||
test('should use index file links in sidebar from index file hosting', async () => {
|
||||
await docsifyInit(sharedOptions);
|
||||
const onceRendered = await docsifyInit(sharedOptions);
|
||||
|
||||
await page.click('a[href="#/quickstart"]');
|
||||
await onceRendered();
|
||||
await expect(page).toHaveText('#main', 'Quick start');
|
||||
expect(page.url()).toMatch(/index\.html#\/quickstart$/);
|
||||
expect(page.url()).toMatch(sharedOptions.testURL + 'quickstart');
|
||||
});
|
||||
});
|
||||
|
||||
@ -39,32 +39,39 @@ describe('Sidebar Tests', function() {
|
||||
},
|
||||
};
|
||||
|
||||
await docsifyInit(docsifyInitConfig);
|
||||
const onceRendered = await docsifyInit(docsifyInitConfig);
|
||||
|
||||
await page.click('a[href="#/test%20space"]');
|
||||
await expect(page).toEqualText(
|
||||
await onceRendered();
|
||||
await expect(page).toMatchText(
|
||||
'.sidebar-nav li[class=active]',
|
||||
'Test Space'
|
||||
);
|
||||
expect(page.url()).toMatch(/\/test%20space$/);
|
||||
|
||||
await page.click('a[href="#/test_foo"]');
|
||||
await expect(page).toEqualText('.sidebar-nav li[class=active]', 'Test _');
|
||||
await onceRendered();
|
||||
await expect(page).toMatchText('.sidebar-nav li[class=active]', 'Test _');
|
||||
expect(page.url()).toMatch(/\/test_foo$/);
|
||||
|
||||
await page.click('a[href="#/test-foo"]');
|
||||
await expect(page).toEqualText('.sidebar-nav li[class=active]', 'Test -');
|
||||
await onceRendered();
|
||||
await expect(page).toMatchText('.sidebar-nav li[class=active]', 'Test -');
|
||||
expect(page.url()).toMatch(/\/test-foo$/);
|
||||
|
||||
await page.click('a[href="#/test.foo"]');
|
||||
await onceRendered();
|
||||
await expect(page).toMatchText('.sidebar-nav li[class=active]', 'Test .');
|
||||
expect(page.url()).toMatch(/\/test.foo$/);
|
||||
await expect(page).toEqualText('.sidebar-nav li[class=active]', 'Test .');
|
||||
|
||||
await page.click('a[href="#/test>foo"]');
|
||||
await expect(page).toEqualText('.sidebar-nav li[class=active]', 'Test >');
|
||||
await onceRendered();
|
||||
await expect(page).toMatchText('.sidebar-nav li[class=active]', 'Test >');
|
||||
expect(page.url()).toMatch(/\/test%3Efoo$/);
|
||||
|
||||
await page.click('a[href="#/test"]');
|
||||
await expect(page).toEqualText('.sidebar-nav li[class=active]', 'Test');
|
||||
await onceRendered();
|
||||
await expect(page).toMatchText('.sidebar-nav li[class=active]', 'Test');
|
||||
expect(page.url()).toMatch(/\/test$/);
|
||||
});
|
||||
});
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
import mock, { proxy } from 'xhr-mock';
|
||||
import { waitForSelector } from './wait-for';
|
||||
|
||||
// TODO use browser.newPage() instead of re-using the same page every time?
|
||||
|
||||
const axios = require('axios');
|
||||
const prettier = require('prettier');
|
||||
const stripIndent = require('common-tags/lib/stripIndent');
|
||||
@ -11,6 +13,12 @@ const docsifyURL = '/lib/docsify.js'; // Playwright
|
||||
const isJSDOM = 'window' in global;
|
||||
const isPlaywright = 'page' in global;
|
||||
|
||||
jest.setTimeout(35_000);
|
||||
if (isPlaywright) {
|
||||
page.setDefaultNavigationTimeout(30_000);
|
||||
page.setDefaultTimeout(30_000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Jest / Playwright helper for creating custom docsify test sites
|
||||
*
|
||||
@ -33,13 +41,37 @@ const isPlaywright = 'page' in global;
|
||||
* @param {Boolean|Object|String} [options._logHTML] Logs HTML to console after initialization. Accepts CSS selector.
|
||||
* @param {Boolean} [options._logHTML.format=true] Formats HTML output
|
||||
* @param {String} [options._logHTML.selector='html'] CSS selector(s) to match and log HTML for
|
||||
* @returns {Promise}
|
||||
* @returns {Promise<() => Promise<void>>}
|
||||
*/
|
||||
async function docsifyInit(options = {}) {
|
||||
const defaults = {
|
||||
config: {
|
||||
basePath: TEST_HOST,
|
||||
el: '#app',
|
||||
plugins: [
|
||||
/**
|
||||
* This plugin increments a `data-render-count="N"` attribute on the
|
||||
* `<body>` element after each markdown page render. F.e.
|
||||
* `data-render-count="1"`, `data-render-count="2"`, etc.
|
||||
*
|
||||
* The very first render from calling docsifyInit() will result in a
|
||||
* value of 1. Each following render (f.e. from clicking a link) will
|
||||
* increment once rendering of the new page is finished.
|
||||
*
|
||||
* We do this so that we can easily wait for a render to finish before
|
||||
* testing the state of the render result, or else we'll have flaky
|
||||
* tests (a "race condition").
|
||||
*/
|
||||
function RenderCountPlugin(hook) {
|
||||
hook.init(() => {
|
||||
document.body.dataset.renderCount = 0;
|
||||
});
|
||||
|
||||
hook.doneEach(() => {
|
||||
document.body.dataset.renderCount++;
|
||||
});
|
||||
},
|
||||
],
|
||||
},
|
||||
html: `
|
||||
<!DOCTYPE html>
|
||||
@ -87,7 +119,15 @@ async function docsifyInit(options = {}) {
|
||||
// Config as function
|
||||
if (typeof options.config === 'function') {
|
||||
return function(vm) {
|
||||
const config = { ...sharedConfig, ...options.config(vm) };
|
||||
const outsideConfig = options.config(vm);
|
||||
const config = {
|
||||
...sharedConfig,
|
||||
...outsideConfig,
|
||||
plugins: [
|
||||
...sharedConfig.plugins,
|
||||
...(outsideConfig?.plugins ?? []),
|
||||
],
|
||||
};
|
||||
|
||||
updateBasePath(config);
|
||||
|
||||
@ -96,7 +136,12 @@ async function docsifyInit(options = {}) {
|
||||
}
|
||||
// Config as object
|
||||
else {
|
||||
const config = { ...sharedConfig, ...options.config };
|
||||
const outsideConfig = options.config;
|
||||
const config = {
|
||||
...sharedConfig,
|
||||
...outsideConfig,
|
||||
plugins: [...sharedConfig.plugins, ...(outsideConfig?.plugins ?? [])],
|
||||
};
|
||||
|
||||
updateBasePath(config);
|
||||
|
||||
@ -360,7 +405,59 @@ async function docsifyInit(options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
// We must use a separate renderCount variable outside of the
|
||||
// RenderCountPlugin, because the plugin runs in scope of the Playwright
|
||||
// browser context, not in scope of this file. We can't read
|
||||
// `document.body.renderCount` directly here.
|
||||
let renderCount = 0;
|
||||
|
||||
/**
|
||||
* Call this function to wait for the render to finish any time you perform an
|
||||
* action that changes Docsify's route and causes a new markdown page to
|
||||
* render.
|
||||
*
|
||||
* This is needed only for e2e tests that run in playwright (so far, at least).
|
||||
*
|
||||
* We use page.waitForSelector here to wait for each render or else there is a
|
||||
* race condition where we will try to observe the state of the page between
|
||||
* clicking a link and when the new content is actually updated.
|
||||
*/
|
||||
async function onceRendered() {
|
||||
// This function is only for playwright tests.
|
||||
if (isJSDOM) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initial case: For the first render, there is a bug
|
||||
// (https://github.com/docsifyjs/docsify/issues/1732) that sometimes causes
|
||||
// doneEach to render twice.
|
||||
if (renderCount === 0) {
|
||||
renderCount = await Promise.race([
|
||||
page.waitForSelector(`body[data-render-count="1"]`).then(() => 1),
|
||||
page.waitForSelector(`body[data-render-count="2"]`).then(() => 2),
|
||||
]);
|
||||
}
|
||||
// Successive cases after first render: doneEach fires once like
|
||||
// normal (so far).
|
||||
else {
|
||||
renderCount++;
|
||||
try {
|
||||
await page.waitForSelector(`body[data-render-count="${renderCount}"]`);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
'Render counting failed because doneEach fired an unexpected number of times (more than once). If this happens, please note this in issue https://github.com/docsifyjs/docsify/issues/1732'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the initial render.
|
||||
await onceRendered();
|
||||
|
||||
// Now all tests need to call this any time they change Docsify navagtion
|
||||
// (f.e. clicking a link) to wait for render.
|
||||
return onceRendered;
|
||||
}
|
||||
|
||||
module.exports = docsifyInit;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user