Skip to content

Commit

Permalink
feat: improve playwright support for electron
Browse files Browse the repository at this point in the history
This patch adds the possibility to disable natively rendered elements
by setting the environment variable THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS to 1.

We need this functionality for testing menu actions or use cases that
involve file choosers using electron playwright.
When the environment variable is set, the app loader enforces the
rendering of HTML menus that playwright can access.

Contributed on behalf of STMicroelectronics

Signed-off-by: Olaf Lessenich <[email protected]>
  • Loading branch information
xai committed May 30, 2023
1 parent 312c7d0 commit f59e791
Show file tree
Hide file tree
Showing 18 changed files with 298 additions and 40 deletions.
26 changes: 26 additions & 0 deletions examples/playwright/configs/playwright.ci.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// *****************************************************************************
// Copyright (C) 2022 EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

import { PlaywrightTestConfig } from '@playwright/test';
import baseConfig from './playwright.config';

const ciConfig: PlaywrightTestConfig = {
...baseConfig,
workers: 1,
retries: 1
};

export default ciConfig;
37 changes: 37 additions & 0 deletions examples/playwright/configs/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// *****************************************************************************
// Copyright (C) 2021 logi.cals GmbH, EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

import { PlaywrightTestConfig } from '@playwright/test';

const config: PlaywrightTestConfig = {
testDir: '../lib/tests',
testMatch: ['**/*.js'],
workers: 2,
// Timeout for each test in milliseconds.
timeout: 60 * 1000,
use: {
baseURL: 'http://localhost:3000',
browserName: 'chromium',
screenshot: 'only-on-failure'
},
preserveOutput: 'failures-only',
reporter: [
['list'],
['allure-playwright']
]
};

export default config;
27 changes: 27 additions & 0 deletions examples/playwright/configs/playwright.debug.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// *****************************************************************************
// Copyright (C) 2021 logi.cals GmbH, EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

import { PlaywrightTestConfig } from '@playwright/test';

import baseConfig from './playwright.config';

const debugConfig: PlaywrightTestConfig = {
...baseConfig,
workers: 1,
timeout: 15000000
};

export default debugConfig;
30 changes: 30 additions & 0 deletions examples/playwright/configs/playwright.headful.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// *****************************************************************************
// Copyright (C) 2021 logi.cals GmbH, EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

import { PlaywrightTestConfig } from '@playwright/test';

import baseConfig from './playwright.config';

const headfulConfig: PlaywrightTestConfig = {
...baseConfig,
workers: 1,
use: {
...baseConfig.use,
headless: false
}
};

export default headfulConfig;
7 changes: 7 additions & 0 deletions examples/playwright/configs/ui-tests.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
// override existing rules for ui-tests package
"rules": {
"no-undef": "off", // disabled due to 'browser', '$', '$$'
"no-unused-expressions": "off"
}
}
6 changes: 6 additions & 0 deletions examples/playwright/configs/ui-tests.playwright.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
// override existing rules for ui-tests playwright package
"rules": {
"no-null/no-null": "off"
}
}
5 changes: 3 additions & 2 deletions examples/playwright/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
"lint": "eslint -c ./.eslintrc.js --ext .ts ./src",
"lint:fix": "eslint -c ./.eslintrc.js --ext .ts ./src --fix",
"playwright:install": "playwright install chromium",
"ui-tests": "yarn build && playwright test",
"ui-tests-headful": "yarn build && playwright test --headed",
"ui-tests": "yarn build && playwright test --config=./configs/playwright.config.ts",
"ui-tests-ci": "yarn build && playwright test --config=./configs/playwright.ci.config.ts",
"ui-tests-headful": "yarn build && playwright test --config=./configs/playwright.headful.config.ts",
"ui-tests-report-generate": "allure generate ./allure-results --clean -o allure-results/allure-report",
"ui-tests-report": "yarn ui-tests-report-generate && allure open allure-results/allure-report"
},
Expand Down
74 changes: 66 additions & 8 deletions examples/playwright/src/tests/theia-electron-app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,96 @@ import { TheiaExplorerView } from '../theia-explorer-view';
import { TheiaAboutDialog } from '../theia-about-dialog';
import { ElectronLaunchOptions, TheiaElectronAppLoader } from '../theia-app-loader';
import { TheiaWorkspace } from '../theia-workspace';
import { TheiaApp } from '../theia-app';

test.describe.configure({ mode: 'serial' });
test.describe('Theia Electron Application', () => {

let app: TheiaApp;
let ws: TheiaWorkspace;

test.beforeAll(async () => {
ws = new TheiaWorkspace(['src/tests/resources/sample-files1']);
app = await TheiaElectronAppLoader.load(new ElectronLaunchOptions('../electron', '../../plugins'), ws);
});

test.afterAll(async () => {
await app.page.close();
});

test('should load and show main content panel', async () => {
const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']);
const app = await TheiaElectronAppLoader.load(new ElectronLaunchOptions('../electron', '../../plugins'), ws);
expect(await app.isMainContentPanelVisible()).toBe(true);
});

const quickCommand = app.quickCommandPalette;
test('open about dialog using menu', async () => {
await (await app.menuBar.openMenu('Help')).clickMenuItem('About');
const aboutDialog = new TheiaAboutDialog(app);
expect(await aboutDialog.isVisible()).toBe(true);
await aboutDialog.page.getByRole('button', { name: 'OK' }).click();
expect(await aboutDialog.isVisible()).toBe(false);
});

await quickCommand.open();
expect(await quickCommand.isOpen()).toBe(true);
test('open file via file menu and cancel', async () => {
await (await app.menuBar.openMenu('File')).clickMenuItem('Open File...');
const fileDialog = await app.page.waitForSelector('div[class="dialogBlock"]');
expect(await fileDialog.isVisible()).toBe(true);
await app.page.getByRole('button', { name: 'Cancel' }).click();
expect(await fileDialog.isVisible()).toBe(false);
});

test('open sample.txt via file menu', async () => {
const menuEntry = 'Open File...';

await (await app.menuBar.openMenu('File')).clickMenuItem(menuEntry);

const fileDialog = await app.page.waitForSelector('div[class="dialogBlock"]');
expect(await fileDialog.isVisible()).toBe(true);

const fileEntry = app.page.getByText('sample.txt');
await fileEntry.click();
await app.page.getByRole('button', { name: 'Open' }).click();

const span = await app.page.waitForSelector('span:has-text("content line 2")');
expect(await span.isVisible()).toBe(true);
});

test('open about dialog using command', async () => {
const quickCommand = app.quickCommandPalette;
await quickCommand.open();
await quickCommand.type('About');
await quickCommand.trigger('About');
expect(await quickCommand.isOpen()).toBe(false);
const aboutDialog = new TheiaAboutDialog(app);
expect(await aboutDialog.isVisible()).toBe(true);
await aboutDialog.close();
await aboutDialog.page.getByRole('button', { name: 'OK' }).click();
expect(await aboutDialog.isVisible()).toBe(false);
});

test('select all using command', async () => {
const quickCommand = app.quickCommandPalette;
await quickCommand.type('Select All');
await quickCommand.trigger('Select All');
expect(await quickCommand.isOpen()).toBe(false);
});

test('toggle explorer view using command', async () => {
const quickCommand = app.quickCommandPalette;
await quickCommand.open();
await quickCommand.type('Toggle Explorer');
await quickCommand.trigger('Toggle Explorer View');
expect(await quickCommand.isOpen()).toBe(false);
const explorerView = new TheiaExplorerView(app);
expect(await explorerView.isDisplayed()).toBe(true);
await quickCommand.open();
await quickCommand.type('Toggle Explorer');
await quickCommand.trigger('Toggle Explorer View');
expect(await explorerView.isDisplayed()).toBe(false);
});

test('toggle explorer view using menu', async () => {
await (await app.menuBar.openMenu('View')).clickMenuItem('Explorer');
const explorerView = new TheiaExplorerView(app);
expect(await explorerView.isDisplayed()).toBe(true);
await (await app.menuBar.openMenu('View')).clickMenuItem('Explorer');
expect(await explorerView.isDisplayed()).toBe(false);
});
});

13 changes: 6 additions & 7 deletions examples/playwright/src/tests/theia-output-view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,19 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

import { expect } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { TheiaOutputViewChannel } from 'src/theia-output-channel';
import { TheiaApp } from '../theia-app';
import { TheiaBrowserAppLoader } from '../theia-app-loader';
import { TheiaOutputView } from '../theia-output-view';
import test, { page } from './fixtures/theia-fixture';

let app: TheiaApp;
let outputView: TheiaOutputView;
let testChannel: TheiaOutputViewChannel;
let app: TheiaApp; let outputView: TheiaOutputView; let testChannel: TheiaOutputViewChannel;

test.describe('Theia Output View', () => {

test.beforeAll(async () => {
app = await TheiaApp.load(page);
test.beforeAll(async ({ browser }) => {
const page = await browser.newPage();
app = await TheiaBrowserAppLoader.load(page);
});

test('should open the output view and check if is visible and active', async () => {
Expand Down
9 changes: 5 additions & 4 deletions examples/playwright/src/tests/theia-terminal-view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

import { expect } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { TheiaApp } from '../theia-app';
import { TheiaBrowserAppLoader } from '../theia-app-loader';
import { TheiaWorkspace } from '../theia-workspace';
import test, { page } from './fixtures/theia-fixture';
import { TheiaTerminal } from '../theia-terminal';

let app: TheiaApp;

test.describe('Theia Terminal View', () => {

test.beforeAll(async () => {
test.beforeAll(async ({ browser }) => {
const page = await browser.newPage();
const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']);
app = await TheiaApp.load(page, ws);
app = await TheiaBrowserAppLoader.load(page, ws);
});

test('should be possible to open a new terminal', async () => {
Expand Down
9 changes: 5 additions & 4 deletions examples/playwright/src/tests/theia-toolbar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

import { expect } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { TheiaApp } from '../theia-app';
import { TheiaBrowserAppLoader } from '../theia-app-loader';
import { TheiaToolbar } from '../theia-toolbar';
import test, { page } from './fixtures/theia-fixture';

let app: TheiaApp;
let toolbar: TheiaToolbar;

test.describe('Theia Toolbar', () => {

test.beforeAll(async () => {
app = await TheiaApp.load(page);
test.beforeAll(async ({ browser }) => {
const page = await browser.newPage();
app = await TheiaBrowserAppLoader.load(page);
toolbar = new TheiaToolbar(app);
});

Expand Down
17 changes: 17 additions & 0 deletions examples/playwright/src/tests/theia-workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,21 @@ test.describe('Theia Workspace', () => {
expect(await explorer.existsFileNode('another-sample.txt')).toBe(true);
});

test('open sample.txt via file menu', async ({ page }) => {
const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']);
const app = await TheiaBrowserAppLoader.load(page, ws);
const menuEntry = 'Open...';

await (await app.menuBar.openMenu('File')).clickMenuItem(menuEntry);
const fileDialog = await app.page.waitForSelector('div[class="dialogBlock"]');
expect(await fileDialog.isVisible()).toBe(true);

const fileEntry = app.page.getByText('sample.txt');
await fileEntry.click();
await app.page.getByRole('button', { name: 'Open' }).click();

const span = await app.page.waitForSelector('span:has-text("content line 2")');
expect(await span.isVisible()).toBe(true);
});

});
16 changes: 15 additions & 1 deletion examples/playwright/src/theia-app-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
// *****************************************************************************

import { Page, PlaywrightWorkerArgs, _electron as electron } from '@playwright/test';
import * as path from 'path';
import * as fs from 'fs';
import { TheiaApp, TheiaAppMainPageObjects } from './theia-app';
import { TheiaWorkspace } from './theia-workspace';
import { OSUtil } from './util';

export interface TheiaAppFactory<T extends TheiaApp> {
new(page: Page, initialWorkspace?: TheiaWorkspace, mainPageObjects?: TheiaAppMainPageObjects): T;
Expand Down Expand Up @@ -108,7 +111,16 @@ export class ElectronLaunchOptions {
) { }

playwrightOptions(workspace?: TheiaWorkspace): object {
const executablePath = this.electronAppPath + '/node_modules/.bin/electron';
let executablePath = path.normalize(path.join(this.electronAppPath, 'node_modules/.bin/electron'));
if (OSUtil.isWindows) {
executablePath += '.cmd';
}
if (!fs.existsSync(executablePath)) {
const errorMsg = `executablePath: ${executablePath} does not exist`;
console.log(errorMsg);
throw new Error(errorMsg);
}

const args: string[] = [];
args.push(this.electronAppPath);
args.push(...this.additionalArgs);
Expand All @@ -119,6 +131,8 @@ export class ElectronLaunchOptions {
if (workspace) {
args.push(workspace.path);
}
process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS = '1';
console.log(`Launching Electron: ${executablePath} ${args.join(' ')}`);
return { executablePath, args };
}
}
Expand Down
Loading

0 comments on commit f59e791

Please sign in to comment.