Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up playwright (pt1: preparation) #1717

Merged
merged 5 commits into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions test/data/database-test-cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"ipa": 1
},
"terms": {
"total": 33
"total": 34
}
}
},
Expand All @@ -37,7 +37,7 @@
{
"kanji": 2,
"kanjiMeta": 6,
"terms": 33,
"terms": 34,
"termMeta": 39,
"tagMeta": 15,
"media": 6
Expand All @@ -46,7 +46,7 @@
"total": {
"kanji": 2,
"kanjiMeta": 6,
"terms": 33,
"terms": 34,
"termMeta": 39,
"tagMeta": 15,
"media": 6
Expand Down
3 changes: 2 additions & 1 deletion test/data/dictionaries/valid-dictionary1/term_bank_1.json
Original file line number Diff line number Diff line change
Expand Up @@ -347,5 +347,6 @@
["USB", "ユーエスビー", "n", "n", 1, ["USB definition"], 21, ""],
["마시다", "", "v", "v", 1, ["masida definition"], 22, ""],
["自重", "じちょう", "n", "n", 1, ["jichou definition"], 23, ""],
["自重", "じじゅう", "n", "n", 2, ["jijuu definition"], 24, ""]
["自重", "じじゅう", "n", "n", 2, ["jijuu definition"], 24, ""],
["ありがとう", "ありがとう", "n", "n", 1, ["arigatou definition"], 25, ""]
]
12 changes: 12 additions & 0 deletions test/data/html/js/popup-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,16 @@ HtmlTestUtilities.runMain(() => {
if (!(iframeWithSrcdoc instanceof HTMLIFrameElement)) { continue; }
iframeWithSrcdoc.srcdoc = HtmlTestUtilities.dataUrlToContent(src).content;
}

const testCases = document.querySelectorAll('test-case[data-expected-result="failure"]');
for (const testCase of testCases) {
const description = testCase.querySelector('test-description');
if (description) {
const dangerSpan = document.createElement('span');
dangerSpan.className = 'danger';
dangerSpan.textContent = 'This element is expected to not work.';
description.appendChild(document.createElement('br'));
description.appendChild(dangerSpan);
}
}
});
21 changes: 12 additions & 9 deletions test/data/html/popup-tests.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
.danger {
color: #c83c28;
}


</style>
</head>
<body>
Expand All @@ -53,7 +55,7 @@ <h1>Popup Tests</h1>
</div>
</test-case>

<test-case data-shadow-mode="open">
<test-case data-shadow-mode="open" data-expected-result="failure">
<test-description>Content inside of an open shadow DOM.</test-description>
<div class="template-content-container hovertarget"></div>
<template>
Expand All @@ -71,7 +73,7 @@ <h1>Popup Tests</h1>
</template>
</test-case>

<test-case data-shadow-mode="closed">
<test-case data-shadow-mode="closed" data-expected-result="failure">
<test-description>Content inside of a closed shadow DOM.</test-description>
<div class="template-content-container hovertarget"></div>
<template>
Expand Down Expand Up @@ -110,14 +112,14 @@ <h1>Popup Tests</h1>
</template>
</test-case>

<test-case>
<test-case data-expected-result="failure">
<test-description>&lt;iframe&gt; element with data URL.</test-description>
<iframe id="iframe-with-data-url"
src="data:text/html;base64,PCFET0NUWVBFIGh0bWw+DQo8aHRtbD4NCiAgICA8aGVhZD4NCiAgICAgICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPg0KICAgICAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLGluaXRpYWwtc2NhbGU9MSI+DQogICAgICAgIDx0aXRsZT5Zb21pY2hhbiBUZXN0czwvdGl0bGU+DQogICAgICAgIDxzY3JpcHQ+DQogZnVuY3Rpb24gcmVxdWVzdEZ1bGxzY3JlZW4oZWxlbWVudCkgew0KICAgIGlmIChlbGVtZW50LnJlcXVlc3RGdWxsc2NyZWVuKSB7DQogICAgICAgIGVsZW1lbnQucmVxdWVzdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2UgaWYgKGVsZW1lbnQubW96UmVxdWVzdEZ1bGxTY3JlZW4pIHsNCiAgICAgICAgZWxlbWVudC5tb3pSZXF1ZXN0RnVsbFNjcmVlbigpOw0KICAgIH0gZWxzZSBpZiAoZWxlbWVudC53ZWJraXRSZXF1ZXN0RnVsbHNjcmVlbikgew0KICAgICAgICBlbGVtZW50LndlYmtpdFJlcXVlc3RGdWxsc2NyZWVuKCk7DQogICAgfSBlbHNlIGlmIChlbGVtZW50Lm1zUmVxdWVzdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZWxlbWVudC5tc1JlcXVlc3RGdWxsc2NyZWVuKCk7DQogICAgfQ0KfQ0KDQpmdW5jdGlvbiBleGl0RnVsbHNjcmVlbigpIHsNCiAgICBpZiAoZG9jdW1lbnQuZXhpdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQuZXhpdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2UgaWYgKGRvY3VtZW50Lm1vekNhbmNlbEZ1bGxTY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQubW96Q2FuY2VsRnVsbFNjcmVlbigpOw0KICAgIH0gZWxzZSBpZiAoZG9jdW1lbnQud2Via2l0RXhpdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQud2Via2l0RXhpdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2UgaWYgKGRvY3VtZW50Lm1zRXhpdEZ1bGxzY3JlZW4pIHsNCiAgICAgICAgZG9jdW1lbnQubXNFeGl0RnVsbHNjcmVlbigpOw0KICAgIH0NCn0NCg0KZnVuY3Rpb24gZ2V0RnVsbHNjcmVlbkVsZW1lbnQoKSB7DQogICAgcmV0dXJuICgNCiAgICAgICAgZG9jdW1lbnQuZnVsbHNjcmVlbkVsZW1lbnQgfHwNCiAgICAgICAgZG9jdW1lbnQubXNGdWxsc2NyZWVuRWxlbWVudCB8fA0KICAgICAgICBkb2N1bWVudC5tb3pGdWxsU2NyZWVuRWxlbWVudCB8fA0KICAgICAgICBkb2N1bWVudC53ZWJraXRGdWxsc2NyZWVuRWxlbWVudCB8fA0KICAgICAgICBudWxsDQogICAgKTsNCn0NCg0KZnVuY3Rpb24gdG9nZ2xlRnVsbHNjcmVlbihlbGVtZW50KSB7DQogICAgaWYgKGdldEZ1bGxzY3JlZW5FbGVtZW50KCkpIHsNCiAgICAgICAgZXhpdEZ1bGxzY3JlZW4oKTsNCiAgICB9IGVsc2Ugew0KICAgICAgICByZXF1ZXN0RnVsbHNjcmVlbihlbGVtZW50KTsNCiAgICB9DQp9DQoNCmZ1bmN0aW9uIHNldHVwKGNvbnRhaW5lciwgZnVsbHNjcmVlbkVsZW1lbnQ9bnVsbCkgew0KICAgIGNvbnN0IGZ1bGxzY3JlZW5MaW5rID0gY29udGFpbmVyLnF1ZXJ5U2VsZWN0b3IoJy5mdWxsc2NyZWVuLWxpbmsnKTsNCiAgICBpZiAoZnVsbHNjcmVlbkxpbmsgIT09IG51bGwpIHsNCiAgICAgICAgaWYgKGZ1bGxzY3JlZW5FbGVtZW50ID09PSBudWxsKSB7DQogICAgICAgICAgICBmdWxsc2NyZWVuRWxlbWVudCA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCcuZnVsbHNjcmVlbi1lbGVtZW50Jyk7DQogICAgICAgIH0NCiAgICAgICAgZnVsbHNjcmVlbkxpbmsuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoZSkgPT4gew0KICAgICAgICAgICAgdG9nZ2xlRnVsbHNjcmVlbihmdWxsc2NyZWVuRWxlbWVudCk7DQogICAgICAgICAgICBlLnByZXZlbnREZWZhdWx0KCk7DQogICAgICAgICAgICByZXR1cm4gZmFsc2U7DQogICAgICAgIH0sIGZhbHNlKTsNCiAgICB9DQoNCiAgICBjb25zdCB0ZW1wbGF0ZSA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCd0ZW1wbGF0ZScpOw0KICAgIGNvbnN0IHRlbXBsYXRlQ29udGVudENvbnRhaW5lciA9IGNvbnRhaW5lci5xdWVyeVNlbGVjdG9yKCcudGVtcGxhdGUtY29udGVudC1jb250YWluZXInKTsNCiAgICBpZiAodGVtcGxhdGUgIT09IG51bGwgJiYgdGVtcGxhdGVDb250ZW50Q29udGFpbmVyICE9PSBudWxsKSB7DQogICAgICAgIGNvbnN0IG1vZGUgPSBjb250YWluZXIuZGF0YXNldC5zaGFkb3dNb2RlOw0KICAgICAgICBjb25zdCBzaGFkb3cgPSB0ZW1wbGF0ZUNvbnRlbnRDb250YWluZXIuYXR0YWNoU2hhZG93KHttb2RlfSk7DQoNCiAgICAgICAgY29uc3QgY29udGFpbmVyU3R5bGVzID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcignI2NvbnRhaW5lci1zdHlsZXMnKTsNCiAgICAgICAgc2hhZG93LmFwcGVuZENoaWxkKGNvbnRhaW5lclN0eWxlcy5jbG9uZU5vZGUodHJ1ZSkpOw0KDQogICAgICAgIGNvbnN0IGNvbnRlbnQgPSBkb2N1bWVudC5pbXBvcnROb2RlKHRlbXBsYXRlLmNvbnRlbnQsIHRydWUpOw0KICAgICAgICBzZXR1cChjb250ZW50KTsNCiAgICAgICAgc2hhZG93LmFwcGVuZENoaWxkKGNvbnRlbnQpOw0KICAgIH0NCn0NCiAgICAgICAgPC9zY3JpcHQ+DQogICAgICAgIDxzdHlsZT4NCmJvZHkgew0KICAgIGZvbnQtZmFtaWx5OiAiSGVsdmV0aWNhIE5ldWUiLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOw0KICAgIGZvbnQtc2l6ZTogMTRweDsNCiAgICBwYWRkaW5nOiAwOw0KICAgIG1hcmdpbjogMDsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmOGY4Ow0KfQ0KYSwgYTp2aXNpdGVkIHsNCiAgICBjb2xvcjogIzEwODBjMDsNCiAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsNCn0NCi5jb250ZW50IHsNCiAgICBwb3NpdGlvbjogYWJzb2x1dGU7DQogICAgbGVmdDogMDsNCiAgICB0b3A6IDA7DQogICAgcmlnaHQ6IDA7DQogICAgYm90dG9tOiAwOw0KICAgIHBhZGRpbmc6IDAuNWVtOw0KICAgIGJhY2tncm91bmQtY29sb3I6ICNmOGY4Zjg7DQp9DQogICAgICAgIDwvc3R5bGU+DQogICAgPC9oZWFkPg0KPGJvZHk+PGRpdiBjbGFzcz0iY29udGVudCI+DQo8ZGl2Pg0KICAgIOOBguOCiuOBjOOBqOOBhg0KPC9kaXY+DQo8ZGl2Pg0KICAgIDxhIGhyZWY9IiMiIGNsYXNzPSJmdWxsc2NyZWVuLWxpbmsiPlRvZ2dsZSBmdWxsc2NyZWVuPC9hPg0KICAgIDxzY3JpcHQ+c2V0dXAoZG9jdW1lbnQuYm9keSwgZG9jdW1lbnQuYm9keSk7PC9zY3JpcHQ+DQo8L2Rpdj4NCjwvZGl2PjwvYm9keT4NCjwvaHRtbD4="
allowfullscreen="true" class="container hovertarget"></iframe>
</test-case>

<test-case>
<test-case data-expected-result="failure">
<test-description>&lt;iframe&gt; element with blob URL.</test-description>
<iframe id="iframe-with-blob-url" allowfullscreen="true" class="container hovertarget"></iframe>
</test-case>
Expand All @@ -134,16 +136,15 @@ <h1>Popup Tests</h1>
sandbox="allow-same-origin allow-scripts"></iframe>
</test-case>

<test-case>
<test-case data-expected-result="failure">
<test-description>
&lt;iframe&gt; element with srcdoc and <code>sandbox="allow-scripts"</code>.<br>
<span class="danger">This element is expected to not work.</span>
&lt;iframe&gt; element with srcdoc and <code>sandbox="allow-scripts"</code>.
</test-description>
<iframe allowfullscreen="true" class="iframe-with-srcdoc container hovertarget"
sandbox="allow-scripts"></iframe>
</test-case>

<test-case>
<test-case data-expected-result="failure">
<test-description>SVG &lt;img&gt;.</test-description>
<img src="popup-tests-frame2.svg" class="container hovertarget" alt="">
</test-case>
Expand All @@ -153,7 +154,7 @@ <h1>Popup Tests</h1>
<object data="popup-tests-frame2.svg" type="image/svg+xml" class="container hovertarget"></object>
</test-case>

<test-case>
<test-case data-expected-result="failure">
<test-description>SVG &lt;embed&gt;.</test-description>
<embed type="image/svg+xml" src="popup-tests-frame2.svg" class="container hovertarget">
</test-case>
Expand All @@ -176,5 +177,7 @@ <h1>Popup Tests</h1>
</text>
</svg>
</test-case>

<div id="footer">Tests Complete</div>
</body>
</html>
64 changes: 41 additions & 23 deletions test/playwright/visual.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import path from 'path';
import {pathToFileURL} from 'url';
import {createDictionaryArchiveData} from '../../dev/dictionary-archive-util.js';
import {expect, root, test} from './playwright-util.js';

test.beforeEach(async ({context}) => {
Expand All @@ -25,15 +26,18 @@ test.beforeEach(async ({context}) => {
await welcome.close(); // Close the welcome tab so our main tab becomes the foreground tab -- otherwise, the screenshot can hang
});

test('visual', async ({page, extensionId}) => {
test('welcome', async ({page, extensionId}) => {
// Open welcome page
console.log('Open welcome page');
await page.goto(`chrome-extension://${extensionId}/welcome.html`);
await expect(page.getByText('Welcome to Yomitan!')).toBeVisible();

// Take a screenshot of the welcome page
await expect.soft(page).toHaveScreenshot('welcome-page.png');

});
test('settings', async ({page, extensionId}) => {
// Open settings
console.log('Open settings');
await page.goto(`chrome-extension://${extensionId}/settings.html`);

await expect(page.locator('id=dictionaries')).toBeVisible();
Expand All @@ -45,6 +49,7 @@ test('visual', async ({page, extensionId}) => {
await expect.soft(page).toHaveScreenshot('settings-fresh.png', {mask: [storage_locator]});

// Load in jmdict_english.zip
console.log('Load in jmdict_english.zip');
await page.locator('input[id="dictionary-import-file-input"]').setInputFiles(path.join(root, 'dictionaries/jmdict_english.zip'));
await expect(page.locator('id=dictionaries')).toHaveText('Dictionaries (1 installed, 1 enabled)', {timeout: 5 * 60 * 1000});

Expand All @@ -56,6 +61,7 @@ test('visual', async ({page, extensionId}) => {
await page.locator('input#advanced-checkbox').evaluate((/** @type {HTMLInputElement} */ element) => element.click());

// Import jmdict_swedish.zip from a URL
console.log('Load in jmdict_swedish.zip');
await page.locator('.settings-item[data-modal-action="show,dictionaries"]').click();
await page.locator('button[id="dictionary-import-button"]').click();
await page.locator('textarea[id="dictionary-import-url-text"]').fill('https://github.com/yomidevs/yomitan/raw/dictionaries/jmdict_swedish.zip');
Expand All @@ -79,24 +85,43 @@ test('visual', async ({page, extensionId}) => {
await page.setViewportSize({width: 1280, height: pageHeight});

// Wait for any animations or changes to complete
console.log('Waiting for animations to complete');
await page.waitForTimeout(500);

// Take a full page screenshot of the settings page with advanced settings enabled
await expect.soft(page).toHaveScreenshot('settings-fresh-full-advanced.png', {
fullPage: true,
mask: [storage_locator],
});
});

test('popup', async ({page, extensionId}) => {
// Open settings
console.log('Open settings');
await page.goto(`chrome-extension://${extensionId}/settings.html`);

await expect(page.locator('id=dictionaries')).toBeVisible();

// Load in test dictionary
const dictionary = await createDictionaryArchiveData(path.join(root, 'test/data/dictionaries/valid-dictionary1'), 'valid-dictionary1');
await page.locator('input[id="dictionary-import-file-input"]').setInputFiles({
name: 'valid-dictionary1.zip',
mimeType: 'application/x-zip',
buffer: Buffer.from(dictionary),
});
await expect(page.locator('id=dictionaries')).toHaveText('Dictionaries (1 installed, 1 enabled)', {timeout: 1 * 60 * 1000});

/**
* @param {number} doc_number
* @param {number} test_number
* @param {import('@playwright/test').ElementHandle<Node>} el
* @param {import('@playwright/test').Locator} hovertarget_locator
* @param {{x: number, y: number}} offset
*/
const screenshot = async (doc_number, test_number, el, offset) => {
const screenshot = async (doc_number, test_number, hovertarget_locator, offset) => {
const test_name = 'doc' + doc_number + '-test' + test_number;
console.log(test_name);

const box = (await el.boundingBox()) || {x: 0, y: 0, width: 0, height: 0};
const box = (await hovertarget_locator.boundingBox()) || {x: 0, y: 0, width: 0, height: 0};

// Find the popup frame if it exists
let popup_frame = page.frames().find((f) => f.url().includes('popup.html'));
Expand All @@ -110,38 +135,31 @@ test('visual', async ({page, extensionId}) => {
if (typeof popup_frame === 'undefined') {
popup_frame = await frame_attached; // Wait for popup to be attached
}

try {
// Some tests don't have a popup, so don't fail if it's not there
// TODO: check if the popup is expected to be there
await (await /** @type {import('@playwright/test').Frame} */ (popup_frame).frameElement()).waitForElementState('visible', {timeout: 500});
const expectedState = (await hovertarget_locator.locator('..').getAttribute('data-expected-result')) === 'failure' ? 'hidden' : 'visible';
await (await /** @type {import('@playwright/test').Frame} */ (popup_frame).frameElement()).waitForElementState(expectedState, {timeout: 500});
} catch (error) {
console.log(test_name + ' has no popup');
console.warn(test_name, 'unexpected popup state');
}

console.log(test_name, 'taking screenshot');
await page.bringToFront(); // Bring the page to the foreground so the screenshot doesn't hang; for some reason the frames result in page being in the background
await expect.soft(page).toHaveScreenshot(test_name + '.png');

console.log(test_name, 'clicking away and waiting');
await page.mouse.click(0, 0); // Click away so popup disappears
await (await /** @type {import('@playwright/test').Frame} */ (popup_frame).frameElement()).waitForElementState('hidden'); // Wait for popup to disappear
};

// Test document 1
await page.goto(pathToFileURL(path.join(root, 'test/data/html/document-util.html')).toString());
await page.setViewportSize({width: 1000, height: 1800});
await page.keyboard.down('Shift');
let i = 1;
for (const el of await page.locator('div > *:nth-child(1)').elementHandles()) {
await screenshot(1, i, el, {x: 6, y: 6});
i++;
}

// Test document 2
console.log('Open popup-tests.html');
await page.goto(pathToFileURL(path.join(root, 'test/data/html/popup-tests.html')).toString());
await page.setViewportSize({width: 1000, height: 4500});
await expect(page.locator('id=footer')).toBeVisible();
await page.keyboard.down('Shift');
i = 1;
for (const el of await page.locator('.hovertarget').elementHandles()) {
await screenshot(2, i, el, {x: 15, y: 15});
let i = 1;
for (const test_locator of await page.locator('.hovertarget').all()) {
await screenshot(2, i, test_locator, {x: 15, y: 15});
i++;
}
});
Loading