diff --git a/addons/addon-web-links/test/WebLinksAddon.api.ts b/addons/addon-web-links/test/WebLinksAddon.test.ts similarity index 65% rename from addons/addon-web-links/test/WebLinksAddon.api.ts rename to addons/addon-web-links/test/WebLinksAddon.test.ts index 99c11600..abad5150 100644 --- a/addons/addon-web-links/test/WebLinksAddon.api.ts +++ b/addons/addon-web-links/test/WebLinksAddon.test.ts @@ -2,18 +2,11 @@ * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ - -import { assert } from 'chai'; -import { openTerminal, pollFor, writeSync, launchBrowser } from '../../../out-test/api/TestUtils'; -import { Browser, Page } from '@playwright/test'; - -const APP = 'http://127.0.0.1:3001/test'; - -let browser: Browser; -let page: Page; -const width = 800; -const height = 600; - +import test from '@playwright/test'; +import { deepStrictEqual, strictEqual } from 'assert'; +import { readFile } from 'fs'; +import { resolve } from 'path'; +import { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../out-test/playwright/TestUtils'; interface ILinkStateData { uri?: string; @@ -30,21 +23,20 @@ interface ILinkStateData { } -describe('WebLinksAddon', () => { - before(async function(): Promise { - browser = await launchBrowser(); - page = await (await browser.newContext()).newPage(); - await page.setViewportSize({ width, height }); - await page.goto(APP); - await openTerminal(page, { cols: 40 }); - }); +let ctx: ITestContext; +test.beforeAll(async ({ browser }) => { + ctx = await createTestContext(browser); + await openTerminal(ctx, { cols: 40 }); +}); +test.afterAll(async () => await ctx.page.close()); - after(async () => await browser.close()); - beforeEach(async () => { - await page.evaluate(` +test.describe('WebLinksAddon', () => { + + test.beforeEach(async () => { + await ctx.page.evaluate(` + window.term.reset() window._linkaddon?.dispose(); - window.term.reset(); - window._linkaddon = new window.WebLinksAddon(); + window._linkaddon = new WebLinksAddon(); window.term.loadAddon(window._linkaddon); `); }); @@ -71,36 +63,36 @@ describe('WebLinksAddon', () => { '.vc', '.ve', '.vg', '.vi', '.vn', '.vu', '.wf', '.ws', '.ye', '.yt', '.za', '.zm', '.zw' ]; for (const tld of countryTlds) { - it(tld, async () => await testHostName(`foo${tld}`)); + test(tld, async () => await testHostName(`foo${tld}`)); } - it(`.com`, async () => await testHostName(`foo.com`)); + test(`.com`, async () => await testHostName(`foo.com`)); for (const tld of countryTlds) { - it(`.com${tld}`, async () => await testHostName(`foo.com${tld}`)); + test(`.com${tld}`, async () => await testHostName(`foo.com${tld}`)); } - describe('correct buffer offsets & uri', () => { - beforeEach(async () => { - await page.evaluate(` + test.describe('correct buffer offsets & uri', () => { + test.beforeEach(async () => { + await ctx.page.evaluate(` window._linkStateData = {uri:''}; window._linkaddon._options.hover = (event, uri, range) => { window._linkStateData = { uri, range }; }; `); }); - it('all half width', async () => { - await writeSync(page, 'aaa http://example.com aaa http://example.com aaa'); + test('all half width', async () => { + await ctx.proxy.write('aaa http://example.com aaa http://example.com aaa'); await resetAndHover(5, 0); await evalLinkStateData('http://example.com', { start: { x: 5, y: 1 }, end: { x: 22, y: 1 } }); await resetAndHover(1, 1); await evalLinkStateData('http://example.com', { start: { x: 28, y: 1 }, end: { x: 5, y: 2 } }); }); - it('url after full width', async () => { - await writeSync(page, '¥¥¥ http://example.com ¥¥¥ http://example.com aaa'); + test('url after full width', async () => { + await ctx.proxy.write('¥¥¥ http://example.com ¥¥¥ http://example.com aaa'); await resetAndHover(8, 0); await evalLinkStateData('http://example.com', { start: { x: 8, y: 1 }, end: { x: 25, y: 1 } }); await resetAndHover(1, 1); await evalLinkStateData('http://example.com', { start: { x: 34, y: 1 }, end: { x: 11, y: 2 } }); }); - it('full width within url and before', async () => { - await writeSync(page, '¥¥¥ https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 ¥¥¥'); + test('full width within url and before', async () => { + await ctx.proxy.write('¥¥¥ https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 ¥¥¥'); await resetAndHover(8, 0); await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 8, y: 1 }, end: { x: 11, y: 2 } }); await resetAndHover(1, 1); @@ -108,15 +100,15 @@ describe('WebLinksAddon', () => { await resetAndHover(17, 1); await evalLinkStateData('https://ko.wikipedia.org/wiki/위키백과:대문', { start: { x: 17, y: 2 }, end: { x: 19, y: 3 } }); }); - it('name + password url after full width and combining', async () => { - await writeSync(page, '¥¥¥cafe\u0301 http://test:password@example.com/some_path'); + test('name + password url after full width and combining', async () => { + await ctx.proxy.write('¥¥¥cafe\u0301 http://test:password@example.com/some_path'); await resetAndHover(12, 0); await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } }); await resetAndHover(5, 1); await evalLinkStateData('http://test:password@example.com/some_path', { start: { x: 12, y: 1 }, end: { x: 13, y: 2 } }); }); - it('url encoded params work properly', async () => { - await writeSync(page, '¥¥¥cafe\u0301 http://test:password@example.com/some_path?param=1%202%3'); + test('url encoded params work properly', async () => { + await ctx.proxy.write('¥¥¥cafe\u0301 http://test:password@example.com/some_path?param=1%202%3'); await resetAndHover(12, 0); await evalLinkStateData('http://test:password@example.com/some_path?param=1%202%3', { start: { x: 12, y: 1 }, end: { x: 27, y: 2 } }); await resetAndHover(5, 1); @@ -125,13 +117,14 @@ describe('WebLinksAddon', () => { }); // issue #4964 - it('uppercase in protocol and host, default ports', async () => { - const data = ` HTTP://EXAMPLE.COM \\r\\n` + - ` HTTPS://Example.com \\r\\n` + - ` HTTP://Example.com:80 \\r\\n` + - ` HTTP://Example.com:80/staysUpper \\r\\n` + - ` HTTP://Ab:xY@abc.com:80/staysUpper \\r\\n`; - await writeSync(page, data); + test('uppercase in protocol and host, default ports', async () => { + await ctx.proxy.write( + ` HTTP://EXAMPLE.COM \r\n` + + ` HTTPS://Example.com \r\n` + + ` HTTP://Example.com:80 \r\n` + + ` HTTP://Example.com:80/staysUpper \r\n` + + ` HTTP://Ab:xY@abc.com:80/staysUpper \r\n` + ); await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`); await pollForLinkAtCell(3, 1, `HTTPS://Example.com`); await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`); @@ -141,14 +134,15 @@ describe('WebLinksAddon', () => { }); async function testHostName(hostname: string): Promise { - const data = ` http://${hostname} \\r\\n` + - ` http://${hostname}/a~b#c~d?e~f \\r\\n` + - ` http://${hostname}/colon:test \\r\\n` + - ` http://${hostname}/colon:test: \\r\\n` + - `"http://${hostname}/"\\r\\n` + - `\\'http://${hostname}/\\'\\r\\n` + - `http://${hostname}/subpath/+/id`; - await writeSync(page, data); + await ctx.proxy.write( + ` http://${hostname} \r\n` + + ` http://${hostname}/a~b#c~d?e~f \r\n` + + ` http://${hostname}/colon:test \r\n` + + ` http://${hostname}/colon:test: \r\n` + + `"http://${hostname}/"\r\n` + + `\'http://${hostname}/\'\r\n` + + `http://${hostname}/subpath/+/id` + ); await pollForLinkAtCell(3, 0, `http://${hostname}`); await pollForLinkAtCell(3, 1, `http://${hostname}/a~b#c~d?e~f`); await pollForLinkAtCell(3, 2, `http://${hostname}/colon:test`); @@ -159,28 +153,28 @@ async function testHostName(hostname: string): Promise { } async function pollForLinkAtCell(col: number, row: number, value: string): Promise { - await page.mouse.move(...(await cellPos(col, row))); - await pollFor(page, `!!Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').length`, true); - const text = await page.evaluate(`Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').map(el => el.textContent).join('');`); - assert.deepEqual(text, value); + await ctx.page.mouse.move(...(await cellPos(col, row))); + await pollFor(ctx.page, `!!Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').length`, true); + const text = await ctx.page.evaluate(`Array.from(document.querySelectorAll('.xterm-rows > :nth-child(${row+1}) > span[style]')).filter(el => el.style.textDecoration == 'underline').map(el => el.textContent).join('');`); + deepStrictEqual(text, value); } async function resetAndHover(col: number, row: number): Promise { - await page.mouse.move(0, 0); - await page.evaluate(`window._linkStateData = {uri:''};`); + await ctx.page.mouse.move(0, 0); + await ctx.page.evaluate(`window._linkStateData = {uri:''};`); await new Promise(r => setTimeout(r, 200)); - await page.mouse.move(...(await cellPos(col, row))); - await pollFor(page, `!!window._linkStateData.uri.length`, true); + await ctx.page.mouse.move(...(await cellPos(col, row))); + await pollFor(ctx.page, `!!window._linkStateData.uri.length`, true); } async function evalLinkStateData(uri: string, range: any): Promise { - const data: ILinkStateData = await page.evaluate(`window._linkStateData`); - assert.equal(data.uri, uri); - assert.deepEqual(data.range, range); + const data: ILinkStateData = await ctx.page.evaluate(`window._linkStateData`); + strictEqual(data.uri, uri); + deepStrictEqual(data.range, range); } async function cellPos(col: number, row: number): Promise<[number, number]> { - const coords: any = await page.evaluate(` + const coords: any = await ctx.page.evaluate(` (function() { const rect = window.term.element.getBoundingClientRect(); const dim = term._core._renderService.dimensions; diff --git a/addons/addon-web-links/test/playwright.config.ts b/addons/addon-web-links/test/playwright.config.ts new file mode 100644 index 00000000..b0e565c5 --- /dev/null +++ b/addons/addon-web-links/test/playwright.config.ts @@ -0,0 +1,35 @@ +import { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + testDir: '.', + timeout: 10000, + projects: [ + { + name: 'Chrome Stable', + use: { + browserName: 'chromium', + channel: 'chrome' + } + }, + { + name: 'Firefox Stable', + use: { + browserName: 'firefox' + } + }, + { + name: 'WebKit', + use: { + browserName: 'webkit' + } + } + ], + reporter: 'list', + webServer: { + command: 'npm run start-server-only', + port: 3000, + timeout: 120000, + reuseExistingServer: !process.env.CI + } +}; +export default config; diff --git a/addons/addon-web-links/test/tsconfig.json b/addons/addon-web-links/test/tsconfig.json index 2b4ec58f..cb6fed28 100644 --- a/addons/addon-web-links/test/tsconfig.json +++ b/addons/addon-web-links/test/tsconfig.json @@ -3,20 +3,37 @@ "module": "commonjs", "target": "es2021", "lib": [ - "es2015" + "es2021", ], "rootDir": ".", "outDir": "../out-test", "sourceMap": true, "removeComments": true, + "baseUrl": ".", + "paths": { + "common/*": [ + "../../../src/common/*" + ], + "browser/*": [ + "../../../src/browser/*" + ] + }, "strict": true, "types": [ - "../../../node_modules/@types/mocha", - "../../../node_modules/@types/node" + "../../../node_modules/@types/node", + "../../../out-test/playwright/TestUtils" ] }, "include": [ "./**/*", "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/common" + }, + { + "path": "../../../src/browser" + } ] } diff --git a/bin/test_playwright.js b/bin/test_playwright.js index 7ba7a8f5..c48ccdb6 100644 --- a/bin/test_playwright.js +++ b/bin/test_playwright.js @@ -27,6 +27,7 @@ let configs = [ { name: 'addon-serialize', path: 'addons/addon-serialize/out-test/playwright.config.js' }, { name: 'addon-unicode-graphemes', path: 'addons/addon-unicode-graphemes/out-test/playwright.config.js' }, { name: 'addon-unicode11', path: 'addons/addon-unicode11/out-test/playwright.config.js' }, + { name: 'addon-web-links', path: 'addons/addon-web-links/out-test/playwright.config.js' }, { name: 'addon-webgl', path: 'addons/addon-webgl/out-test/playwright.config.js' } ];