diff --git a/test/electron/electron-app.spec.ts b/test/electron/electron-app.spec.ts deleted file mode 100644 index 9e3cd9a313..0000000000 --- a/test/electron/electron-app.spec.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import path from 'path'; -import { folio } from './electron.fixture'; -const { it, expect, describe } = folio; - -describe('electron app', (suite, { browserName }) => { - suite.skip(browserName !== 'chromium'); -}, () => { - it('should fire close event', async ({ playwright }) => { - const electronApp = await playwright._electron.launch({ - args: [path.join(__dirname, 'testApp.js')], - }); - const events = []; - electronApp.on('close', () => events.push('application')); - electronApp.context().on('close', () => events.push('context')); - await electronApp.close(); - expect(events.join('|')).toBe('context|application'); - // Give it some time to fire more events - there should not be any. - await new Promise(f => setTimeout(f, 1000)); - expect(events.join('|')).toBe('context|application'); - }); - - it('should script application', async ({ electronApp }) => { - const appPath = await electronApp.evaluate(async ({ app }) => app.getAppPath()); - expect(appPath).toContain('electron'); - }); - - it('should return windows', async ({ electronApp, newWindow }) => { - const window = await newWindow(); - expect(electronApp.windows()).toEqual([window]); - }); - - it('should evaluate handle', async ({ electronApp }) => { - const appHandle = await electronApp.evaluateHandle(({ app }) => app); - expect(await electronApp.evaluate(({ app }, appHandle) => app === appHandle, appHandle)).toBeTruthy(); - }); - - it('should route network', async ({ electronApp, newWindow }) => { - await electronApp.context().route('**/empty.html', (route, request) => { - route.fulfill({ - status: 200, - contentType: 'text/html', - body: 'Hello World', - }); - }); - const window = await newWindow(); - await window.goto('https://localhost:1000/empty.html'); - expect(await window.title()).toBe('Hello World'); - }); - - it('should support init script', async ({ electronApp, newWindow }) => { - await electronApp.context().addInitScript('window.magic = 42;'); - const window = await newWindow(); - await window.goto('data:text/html,'); - expect(await window.evaluate(() => window['copy'])).toBe(42); - }); - - it('should expose function', async ({ electronApp, newWindow }) => { - await electronApp.context().exposeFunction('add', (a, b) => a + b); - const window = await newWindow(); - await window.goto('data:text/html,'); - expect(await window.evaluate(() => window['result'])).toBe(42); - }); - - it('should wait for first window', async ({ electronApp }) => { - await electronApp.evaluate(({ BrowserWindow }) => { - const window = new BrowserWindow({ width: 800, height: 600 }); - window.loadURL('data:text/html,Hello World!'); - }); - const window = await electronApp.firstWindow(); - expect(await window.title()).toBe('Hello World!'); - }); - - it('should have a clipboard instance', async ({ electronApp }) => { - const clipboardContentToWrite = 'Hello from Playwright'; - await electronApp.evaluate(async ({clipboard}, text) => clipboard.writeText(text), clipboardContentToWrite); - const clipboardContentRead = await electronApp.evaluate(async ({clipboard}) => clipboard.readText()); - expect(clipboardContentRead).toEqual(clipboardContentToWrite); - }); -}); diff --git a/test/electron/electron-window.spec.ts b/test/electron/electron-window.spec.ts deleted file mode 100644 index d0737c7fb0..0000000000 --- a/test/electron/electron-window.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { folio } from './electron.fixture'; -const { it, expect, describe } = folio; - -describe('electron window', (suite, { browserName }) => { - suite.skip(browserName !== 'chromium'); -}, () => { - it('should click the button', async ({window, server}) => { - await window.goto(server.PREFIX + '/input/button.html'); - await window.click('button'); - expect(await window.evaluate('result')).toBe('Clicked'); - }); - - it('should check the box', async ({window}) => { - await window.setContent(``); - await window.check('input'); - expect(await window.evaluate('checkbox.checked')).toBe(true); - }); - - it('should not check the checked box', async ({window}) => { - await window.setContent(``); - await window.check('input'); - expect(await window.evaluate('checkbox.checked')).toBe(true); - }); - - it('should type into a textarea', async ({window, server}) => { - await window.evaluate(() => { - const textarea = document.createElement('textarea'); - document.body.appendChild(textarea); - textarea.focus(); - }); - const text = 'Hello world. I am the text that was typed!'; - await window.keyboard.type(text); - expect(await window.evaluate(() => document.querySelector('textarea').value)).toBe(text); - }); -}); diff --git a/test/electron/electron.fixture.ts b/test/electron/electron.fixture.ts deleted file mode 100644 index 528448ac53..0000000000 --- a/test/electron/electron.fixture.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright Microsoft Corporation. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { folio as base } from '../fixtures'; -import path from 'path'; -import { ElectronApplication, Page } from '../..'; - -type TestState = { - electronApp: ElectronApplication; - window: Page; - newWindow: () => Promise; -}; -const fixtures = base.extend(); - -fixtures.electronApp.init(async ({ playwright }, run) => { - const application = await playwright._electron.launch({ - args: [path.join(__dirname, 'testApp.js')], - }); - await run(application); - await application.close(); -}); - -fixtures.newWindow.init(async ({ electronApp }, run) => { - const windows = []; - const newWindow = async () => { - const [ window ] = await Promise.all([ - electronApp.waitForEvent('window'), - electronApp.evaluate(electron => { - const window = new electron.BrowserWindow({ width: 800, height: 600 }); - window.loadURL('data:text/html,Hello World 1'); - }) - ]); - windows.push(window); - return window; - }; - await run(newWindow); - for (const window of windows) - await window.close(); -}); - -fixtures.window.init(async ({ newWindow }, run) => { - await run(await newWindow()); -}); - -export const folio = fixtures.build(); diff --git a/tests/config/default.config.ts b/tests/config/default.config.ts index d16d236f3c..b56032b18d 100644 --- a/tests/config/default.config.ts +++ b/tests/config/default.config.ts @@ -19,8 +19,10 @@ import * as path from 'path'; import { test as playwrightTest, slowTest as playwrightSlowTest } from './playwrightTest'; import { test as browserTest } from './browserTest'; import { test as pageTest } from './pageTest'; +import { test as electronTest } from './electronTest'; import { PlaywrightEnv, BrowserEnv, PageEnv, BrowserName } from './browserEnv'; import { ServerEnv } from './serverEnv'; +import { ElectronEnv } from './electronEnv'; const config: Config = { testDir: path.join(__dirname, '..'), @@ -48,8 +50,9 @@ const serverEnv = new ServerEnv(); const browsers = ['chromium', 'webkit', 'firefox'] as BrowserName[]; for (const browserName of browsers) { const executablePath = getExecutablePath(browserName); + const mode = (process.env.PWMODE || 'default') as ('default' | 'driver' | 'service'); const options = { - mode: (process.env.PWMODE || 'default') as ('default' | 'driver' | 'service'), + mode, executablePath, trace: !!process.env.PWTRACE, headless: !process.env.HEADFUL, @@ -60,4 +63,6 @@ for (const browserName of browsers) { playwrightSlowTest.runWith(browserName, serverEnv, new PlaywrightEnv(browserName, options), { timeout: config.timeout * 3 }); browserTest.runWith(browserName, serverEnv, new BrowserEnv(browserName, options), {}); pageTest.runWith(browserName, serverEnv, new PageEnv(browserName, options), {}); + if (browserName === 'chromium') + electronTest.runWith(browserName, serverEnv, new ElectronEnv({ mode })); } diff --git a/test/electron/testApp.js b/tests/config/electron-app.js similarity index 100% rename from test/electron/testApp.js rename to tests/config/electron-app.js diff --git a/tests/config/electronEnv.ts b/tests/config/electronEnv.ts new file mode 100644 index 0000000000..d991cd73e7 --- /dev/null +++ b/tests/config/electronEnv.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Env, TestInfo } from '../folio/out'; +import { PlaywrightEnv } from './browserEnv'; +import * as path from 'path'; +import { ElectronTestArgs } from './electronTest'; +import { ElectronApplication, Page } from '../../index'; + +export class ElectronEnv extends PlaywrightEnv implements Env { + private _electronApp: ElectronApplication | undefined; + private _windows: Page[] = []; + + constructor(options: { mode: 'default' | 'driver' | 'service' }) { + super('chromium', options); + } + + private async _newWindow() { + const [ window ] = await Promise.all([ + this._electronApp!.waitForEvent('window'), + this._electronApp!.evaluate(electron => { + const window = new electron.BrowserWindow({ width: 800, height: 600 }); + window.loadURL('data:text/html,Hello World 1'); + }) + ]); + this._windows.push(window); + return window; + } + + async beforeEach(testInfo: TestInfo) { + const result = await super.beforeEach(testInfo); + this._electronApp = await result.playwright._electron.launch({ + args: [path.join(__dirname, 'electron-app.js')], + }); + return { + ...result, + electronApp: this._electronApp, + newWindow: this._newWindow.bind(this), + }; + } + + async afterEach(testInfo: TestInfo) { + for (const window of this._windows) + await window.close(); + this._windows = []; + if (this._electronApp) { + await this._electronApp.close(); + this._electronApp = undefined; + } + await super.afterEach(testInfo); + } +} diff --git a/tests/config/electronTest.ts b/tests/config/electronTest.ts new file mode 100644 index 0000000000..cd17ff3214 --- /dev/null +++ b/tests/config/electronTest.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { newTestType } from '../folio/out'; +import { ElectronApplication, Page } from '../../index'; +import type { CommonTestArgs } from './pageTest'; +import type { ServerTestArgs } from './serverTest'; +export { expect } from 'folio'; + +export type ElectronTestArgs = CommonTestArgs & { + electronApp: ElectronApplication; + newWindow: () => Promise; +}; + +export const test = newTestType(); diff --git a/tests/electron/electron-app.spec.ts b/tests/electron/electron-app.spec.ts new file mode 100644 index 0000000000..0d37fb54be --- /dev/null +++ b/tests/electron/electron-app.spec.ts @@ -0,0 +1,90 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import path from 'path'; +import { test, expect } from '../config/electronTest'; + +test('should fire close event', async ({ playwright }) => { + const electronApp = await playwright._electron.launch({ + args: [path.join(__dirname, '..', 'config', 'electron-app.js')], + }); + const events = []; + electronApp.on('close', () => events.push('application')); + electronApp.context().on('close', () => events.push('context')); + await electronApp.close(); + expect(events.join('|')).toBe('context|application'); + // Give it some time to fire more events - there should not be any. + await new Promise(f => setTimeout(f, 1000)); + expect(events.join('|')).toBe('context|application'); +}); + +test('should script application', async ({ electronApp }) => { + const appPath = await electronApp.evaluate(async ({ app }) => app.getAppPath()); + expect(appPath).toBe(path.resolve(__dirname, '..', 'config')); +}); + +test('should return windows', async ({ electronApp, newWindow }) => { + const window = await newWindow(); + expect(electronApp.windows()).toEqual([window]); +}); + +test('should evaluate handle', async ({ electronApp }) => { + const appHandle = await electronApp.evaluateHandle(({ app }) => app); + expect(await electronApp.evaluate(({ app }, appHandle) => app === appHandle, appHandle)).toBeTruthy(); +}); + +test('should route network', async ({ electronApp, newWindow }) => { + await electronApp.context().route('**/empty.html', (route, request) => { + route.fulfill({ + status: 200, + contentType: 'text/html', + body: 'Hello World', + }); + }); + const window = await newWindow(); + await window.goto('https://localhost:1000/empty.html'); + expect(await window.title()).toBe('Hello World'); +}); + +test('should support init script', async ({ electronApp, newWindow }) => { + await electronApp.context().addInitScript('window.magic = 42;'); + const window = await newWindow(); + await window.goto('data:text/html,'); + expect(await window.evaluate(() => window['copy'])).toBe(42); +}); + +test('should expose function', async ({ electronApp, newWindow }) => { + await electronApp.context().exposeFunction('add', (a, b) => a + b); + const window = await newWindow(); + await window.goto('data:text/html,'); + expect(await window.evaluate(() => window['result'])).toBe(42); +}); + +test('should wait for first window', async ({ electronApp }) => { + await electronApp.evaluate(({ BrowserWindow }) => { + const window = new BrowserWindow({ width: 800, height: 600 }); + window.loadURL('data:text/html,Hello World!'); + }); + const window = await electronApp.firstWindow(); + expect(await window.title()).toBe('Hello World!'); +}); + +test('should have a clipboard instance', async ({ electronApp }) => { + const clipboardContentToWrite = 'Hello from Playwright'; + await electronApp.evaluate(async ({clipboard}, text) => clipboard.writeText(text), clipboardContentToWrite); + const clipboardContentRead = await electronApp.evaluate(async ({clipboard}) => clipboard.readText()); + expect(clipboardContentRead).toEqual(clipboardContentToWrite); +}); diff --git a/tests/electron/electron-window.spec.ts b/tests/electron/electron-window.spec.ts new file mode 100644 index 0000000000..3d9b889626 --- /dev/null +++ b/tests/electron/electron-window.spec.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test, expect } from '../config/electronTest'; + +test('should click the button', async ({newWindow, server}) => { + const window = await newWindow(); + await window.goto(server.PREFIX + '/input/button.html'); + await window.click('button'); + expect(await window.evaluate('result')).toBe('Clicked'); +}); + +test('should check the box', async ({newWindow}) => { + const window = await newWindow(); + await window.setContent(``); + await window.check('input'); + expect(await window.evaluate('checkbox.checked')).toBe(true); +}); + +test('should not check the checked box', async ({newWindow}) => { + const window = await newWindow(); + await window.setContent(``); + await window.check('input'); + expect(await window.evaluate('checkbox.checked')).toBe(true); +}); + +test('should type into a textarea', async ({newWindow}) => { + const window = await newWindow(); + await window.evaluate(() => { + const textarea = document.createElement('textarea'); + document.body.appendChild(textarea); + textarea.focus(); + }); + const text = 'Hello world. I am the text that was typed!'; + await window.keyboard.type(text); + expect(await window.evaluate(() => document.querySelector('textarea').value)).toBe(text); +});