diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/playwright/global.setup.js | 32 | ||||
| -rw-r--r-- | test/playwright/global.teardown.js | 28 | ||||
| -rw-r--r-- | test/playwright/integration.spec.js | 91 | ||||
| -rw-r--r-- | test/playwright/playwright-util.js | 109 | ||||
| -rw-r--r-- | test/playwright/visual.spec.js | 38 | 
5 files changed, 269 insertions, 29 deletions
| diff --git a/test/playwright/global.setup.js b/test/playwright/global.setup.js new file mode 100644 index 00000000..442647f8 --- /dev/null +++ b/test/playwright/global.setup.js @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023  Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const {test: setup} = require('@playwright/test'); +const {ManifestUtil} = require('../../dev/manifest-util'); +const {root} = require('./playwright-util'); +const path = require('path'); +const fs = require('fs'); + +const manifestPath = path.join(root, 'ext/manifest.json'); +const copyManifestPath = path.join(root, 'ext/manifest-old.json'); + +setup('use test manifest', () => { +    const manifestUtil = new ManifestUtil(); +    const variant = manifestUtil.getManifest('chrome-playwright'); +    fs.renameSync(manifestPath, copyManifestPath); +    fs.writeFileSync(manifestPath, ManifestUtil.createManifestString(variant).replace('$YOMITAN_VERSION', '0.0.0.0')); +});
\ No newline at end of file diff --git a/test/playwright/global.teardown.js b/test/playwright/global.teardown.js new file mode 100644 index 00000000..2fb29ebe --- /dev/null +++ b/test/playwright/global.teardown.js @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023  Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const {test: teardown} = require('@playwright/test'); +const {root} = require('./playwright-util'); +const path = require('path'); +const fs = require('fs'); + +const manifestPath = path.join(root, 'ext/manifest.json'); +const copyManifestPath = path.join(root, 'ext/manifest-old.json'); + +teardown('bring back original manifest', () => { +    fs.renameSync(copyManifestPath, manifestPath); +});
\ No newline at end of file diff --git a/test/playwright/integration.spec.js b/test/playwright/integration.spec.js new file mode 100644 index 00000000..4e4663d6 --- /dev/null +++ b/test/playwright/integration.spec.js @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023  Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const path = require('path'); +const { +    test, +    expect, +    root, +    mockModelFieldNames, +    mockModelFieldsToAnkiValues, +    expectedAddNoteBody, +    mockAnkiRouteHandler, +    writeToClipboardFromPage +} = require('./playwright-util'); +const {createDictionaryArchive} = require('../../dev/util'); + +test.beforeEach(async ({context}) => { +    // wait for the on-install welcome.html tab to load, which becomes the foreground tab +    const welcome = await context.waitForEvent('page'); +    welcome.close(); // close the welcome tab so our main tab becomes the foreground tab -- otherwise, the screenshot can hang +}); + +test('search clipboard', async ({page, extensionId}) => { +    await page.goto(`chrome-extension://${extensionId}/search.html`); +    await page.locator('#search-option-clipboard-monitor-container > label').click(); +    await page.waitForTimeout(200); // race + +    await writeToClipboardFromPage(page, 'あ'); +    await expect(page.locator('#search-textbox')).toHaveValue('あ'); +}); + +test('anki add', async ({context, page, extensionId}) => { +    // mock anki routes +    let resolve; +    const addNotePromise = new Promise((res) => { +        resolve = res; +    }); +    await context.route(/127.0.0.1:8765\/*/, (route) => { +        mockAnkiRouteHandler(route); +        const req = route.request(); +        if (req.url().includes('127.0.0.1:8765') && req.postDataJSON().action === 'addNote') { +            resolve(req.postDataJSON()); +        } +    }); + +    // open settings +    await page.goto(`chrome-extension://${extensionId}/settings.html`); + +    // load in test dictionary +    const dictionary = createDictionaryArchive(path.join(root, 'test/data/dictionaries/valid-dictionary1'), 'valid-dictionary1'); +    const testDictionarySource = await dictionary.generateAsync({type: 'arraybuffer'}); +    await page.locator('input[id="dictionary-import-file-input"]').setInputFiles({name: 'valid-dictionary1.zip', buffer: Buffer.from(testDictionarySource)}); +    await expect(page.locator('id=dictionaries')).toHaveText('Dictionaries (1 installed, 1 enabled)', {timeout: 5 * 60 * 1000}); + +    // connect to anki +    await page.locator('.toggle', {has: page.locator('[data-setting="anki.enable"]')}).click(); +    await expect(page.locator('#anki-error-message')).toHaveText('Connected'); + +    // prep anki deck +    await page.locator('[data-modal-action="show,anki-cards"]').click(); +    await page.locator('select.anki-card-deck').selectOption('Mock Deck'); +    await page.locator('select.anki-card-model').selectOption('Mock Model'); +    for (const modelField of mockModelFieldNames) { +        await page.locator(`[data-setting="anki.terms.fields.${modelField}"]`).fill(mockModelFieldsToAnkiValues[modelField]); +    } +    await page.locator('#anki-cards-modal > div > div.modal-footer > button:nth-child(2)').click(); +    await writeToClipboardFromPage(page, '読むの例文'); + +    // add to anki deck +    await page.goto(`chrome-extension://${extensionId}/search.html`); +    await page.waitForTimeout(500); // race +    await page.locator('#search-textbox').fill('読む'); +    await page.locator('#search-textbox').press('Enter'); +    await page.locator('[data-mode="term-kanji"]').click(); +    const addNoteReqBody = await addNotePromise; +    expect(addNoteReqBody).toMatchObject(expectedAddNoteBody); +});
\ No newline at end of file diff --git a/test/playwright/playwright-util.js b/test/playwright/playwright-util.js new file mode 100644 index 00000000..e28f16eb --- /dev/null +++ b/test/playwright/playwright-util.js @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023  Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const path = require('path'); +const {test: base, chromium} = require('@playwright/test'); + +export const root = path.join(__dirname, '..', '..'); + +export const test = base.extend({ +    context: async ({ }, use) => { +        const pathToExtension = path.join(root, 'ext'); +        const context = await chromium.launchPersistentContext('', { +            // headless: false, +            args: [ +                '--headless=new', +                `--disable-extensions-except=${pathToExtension}`, +                `--load-extension=${pathToExtension}` +            ] +        }); +        await use(context); +        await context.close(); +    }, +    extensionId: async ({context}, use) => { +        let [background] = context.serviceWorkers(); +        if (!background) { +            background = await context.waitForEvent('serviceworker'); +        } + +        const extensionId = background.url().split('/')[2]; +        await use(extensionId); +    } +}); +export const expect = test.expect; + +export const mockModelFieldNames = [ +    'Word', +    'Reading', +    'Audio', +    'Sentence' +]; + +export const mockModelFieldsToAnkiValues = { +    'Word': '{expression}', +    'Reading': '{furigana-plain}', +    'Sentence': '{clipboard-text}', +    'Audio': '{audio}' +}; + +export const mockAnkiRouteHandler = (route) => { +    const reqBody = route.request().postDataJSON(); +    const respBody = ankiRouteResponses[reqBody.action]; +    if (!respBody) { +        return route.abort(); +    } +    route.fulfill(respBody); +}; + +export const writeToClipboardFromPage = async (page, text) => { +    await page.evaluate(`navigator.clipboard.writeText('${text}')`); +}; + +export const expectedAddNoteBody = { +    'action': 'addNote', +    'params': +    { +        'note': { +            'fields': { +                'Word': '読む', 'Reading': '読[よ]む', 'Audio': '[sound:mock_audio.mp3]', 'Sentence': '読むの例文' +            }, +            'tags': ['yomitan'], +            'deckName': 'Mock Deck', +            'modelName': 'Mock Model', +            'options': { +                'allowDuplicate': false, 'duplicateScope': 'collection', 'duplicateScopeOptions': { +                    'deckName': null, 'checkChildren': false, 'checkAllModels': false +                } +            } +        } +    }, 'version': 2 +}; + +const baseAnkiResp = { +    status: 200, +    contentType: 'text/json' +}; + +const ankiRouteResponses = { +    'version': Object.assign({body: JSON.stringify(6)}, baseAnkiResp), +    'deckNames': Object.assign({body: JSON.stringify(['Mock Deck'])}, baseAnkiResp), +    'modelNames': Object.assign({body: JSON.stringify(['Mock Model'])}, baseAnkiResp), +    'modelFieldNames': Object.assign({body: JSON.stringify(mockModelFieldNames)}, baseAnkiResp), +    'canAddNotes': Object.assign({body: JSON.stringify([true, true])}, baseAnkiResp), +    'storeMediaFile': Object.assign({body: JSON.stringify('mock_audio.mp3')}, baseAnkiResp), +    'addNote': Object.assign({body: JSON.stringify(102312488912)}, baseAnkiResp) +};
\ No newline at end of file diff --git a/test/playwright/visual.spec.js b/test/playwright/visual.spec.js index acb12e97..001f329f 100644 --- a/test/playwright/visual.spec.js +++ b/test/playwright/visual.spec.js @@ -16,40 +16,20 @@   */  const path = require('path'); -const {test: base, chromium} = require('@playwright/test'); -const root = path.join(__dirname, '..', '..'); - -export const test = base.extend({ -    context: async ({ }, use) => { -        const pathToExtension = path.join(root, 'ext'); -        const context = await chromium.launchPersistentContext('', { -            // headless: false, -            args: [ -                '--headless=new', -                `--disable-extensions-except=${pathToExtension}`, -                `--load-extension=${pathToExtension}` -            ] -        }); -        await use(context); -        await context.close(); -    }, -    extensionId: async ({context}, use) => { -        let [background] = context.serviceWorkers(); -        if (!background) { -            background = await context.waitForEvent('serviceworker'); -        } -        const extensionId = background.url().split('/')[2]; -        await use(extensionId); -    } -}); -const expect = test.expect; +const { +    test, +    expect, +    root +} = require('./playwright-util'); -test('visual', async ({context, page, extensionId}) => { +test.beforeEach(async ({context}) => {      // wait for the on-install welcome.html tab to load, which becomes the foreground tab      const welcome = await context.waitForEvent('page');      welcome.close(); // close the welcome tab so our main tab becomes the foreground tab -- otherwise, the screenshot can hang +}); +test('visual', async ({page, extensionId}) => {      // open settings      await page.goto(`chrome-extension://${extensionId}/settings.html`); @@ -117,4 +97,4 @@ test('visual', async ({context, page, extensionId}) => {          await screenshot(2, i, el, {x: 15, y: 15});          i++;      } -}); +});
\ No newline at end of file |