From f10a58bfc7124f41a74cfb709c5ecc5233f33fca Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Fri, 3 Feb 2023 09:58:45 +0100 Subject: [PATCH 01/19] First working version Contributed on behalf of STMicroelectronics. Change-Id: I7c817b00262229ab096ecbbacca5c465b9fa6302 --- examples/playwright/src/index.ts | 1 + .../src/tests/fixtures/theia-fixture.ts | 25 --- .../playwright/src/tests/theia-app.test.ts | 15 +- .../src/tests/theia-electron-app.test.ts | 56 ++++++ .../src/tests/theia-explorer-view.test.ts | 21 ++- .../src/tests/theia-main-menu.test.ts | 22 ++- .../src/tests/theia-preference-view.test.ts | 19 +- .../src/tests/theia-problems-view.test.ts | 19 +- .../src/tests/theia-quick-command.test.ts | 23 ++- .../src/tests/theia-sample-app.test.ts | 20 ++- .../src/tests/theia-status-bar.test.ts | 20 ++- .../src/tests/theia-text-editor.test.ts | 21 ++- .../src/tests/theia-workspace.test.ts | 17 +- examples/playwright/src/theia-app-loader.ts | 167 ++++++++++++++++++ examples/playwright/src/theia-app.ts | 51 +++--- .../playwright/src/theia-explorer-view.ts | 1 + 16 files changed, 371 insertions(+), 127 deletions(-) delete mode 100644 examples/playwright/src/tests/fixtures/theia-fixture.ts create mode 100644 examples/playwright/src/tests/theia-electron-app.test.ts create mode 100644 examples/playwright/src/theia-app-loader.ts diff --git a/examples/playwright/src/index.ts b/examples/playwright/src/index.ts index ca00881b5322f..5dd9ebe6a9a75 100644 --- a/examples/playwright/src/index.ts +++ b/examples/playwright/src/index.ts @@ -16,6 +16,7 @@ export * from './theia-about-dialog'; export * from './theia-app'; +export * from './theia-app-loader'; export * from './theia-context-menu'; export * from './theia-dialog'; export * from './theia-editor'; diff --git a/examples/playwright/src/tests/fixtures/theia-fixture.ts b/examples/playwright/src/tests/fixtures/theia-fixture.ts deleted file mode 100644 index 3c1d27efde1c7..0000000000000 --- a/examples/playwright/src/tests/fixtures/theia-fixture.ts +++ /dev/null @@ -1,25 +0,0 @@ -// ***************************************************************************** -// 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-only WITH Classpath-exception-2.0 -// ***************************************************************************** - -import test, { Page } from '@playwright/test'; - -export let page: Page; - -test.beforeAll(async ({ browser }) => { - page = await browser.newPage(); -}); - -export default test; diff --git a/examples/playwright/src/tests/theia-app.test.ts b/examples/playwright/src/tests/theia-app.test.ts index 157e279899d83..7316f5afeec3b 100644 --- a/examples/playwright/src/tests/theia-app.test.ts +++ b/examples/playwright/src/tests/theia-app.test.ts @@ -14,20 +14,13 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { TheiaApp } from '../theia-app'; - -import { expect } from '@playwright/test'; -import test, { page } from './fixtures/theia-fixture'; - -let app: TheiaApp; +import { expect, test } from '@playwright/test'; +import { TheiaBrowserAppLoader } from '../theia-app-loader'; test.describe('Theia Application', () => { - test('should load', async () => { - app = await TheiaApp.load(page); - }); - - test('should show main content panel', async () => { + test('should load and should show main content panel', async ({ page }) => { + const app = await TheiaBrowserAppLoader.load(page); expect(await app.isMainContentPanelVisible()).toBe(true); }); diff --git a/examples/playwright/src/tests/theia-electron-app.test.ts b/examples/playwright/src/tests/theia-electron-app.test.ts new file mode 100644 index 0000000000000..afbdbeb36c2bb --- /dev/null +++ b/examples/playwright/src/tests/theia-electron-app.test.ts @@ -0,0 +1,56 @@ +// ***************************************************************************** +// Copyright (C) 2022 STMicroelectronics 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 { expect, test } from '@playwright/test'; +import { TheiaExplorerView } from '../theia-explorer-view'; +import { TheiaAboutDialog } from '../theia-about-dialog'; +import { ElectronLaunchOptions, TheiaElectronAppLoader } from '../theia-app-loader'; +import { TheiaWorkspace } from '../theia-workspace'; + +test.describe('Theia Electron Application', () => { + + 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; + + await quickCommand.open(); + expect(await quickCommand.isOpen()).toBe(true); + + 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(); + expect(await aboutDialog.isVisible()).toBe(false); + + await quickCommand.type('Select All'); + await quickCommand.trigger('Select All'); + expect(await quickCommand.isOpen()).toBe(false); + + 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); + }); + +}); diff --git a/examples/playwright/src/tests/theia-explorer-view.test.ts b/examples/playwright/src/tests/theia-explorer-view.test.ts index 6ea87938d7acb..2cbb43a2d83a5 100644 --- a/examples/playwright/src/tests/theia-explorer-view.test.ts +++ b/examples/playwright/src/tests/theia-explorer-view.test.ts @@ -14,24 +14,31 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; +import { TheiaBrowserAppLoader } from '../theia-app-loader'; import { TheiaApp } from '../theia-app'; import { DOT_FILES_FILTER, TheiaExplorerView } from '../theia-explorer-view'; import { TheiaWorkspace } from '../theia-workspace'; -import test, { page } from './fixtures/theia-fixture'; - -let app: TheiaApp; -let explorer: TheiaExplorerView; +// the tests in this file reuse a page to run faster and thus are executed serially +test.describe.configure({ mode: 'serial' }); test.describe('Theia Explorer View', () => { - test.beforeAll(async () => { + let app: TheiaApp; + let explorer: TheiaExplorerView; + + 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); explorer = await app.openView(TheiaExplorerView); await explorer.waitForVisibleFileNodes(); }); + test.afterAll(async () => { + await app.page.close(); + }); + test('should be visible and active after being opened', async () => { expect(await explorer.isTabVisible()).toBe(true); expect(await explorer.isDisplayed()).toBe(true); diff --git a/examples/playwright/src/tests/theia-main-menu.test.ts b/examples/playwright/src/tests/theia-main-menu.test.ts index cc305bed2696d..7e8c78c27f2f5 100644 --- a/examples/playwright/src/tests/theia-main-menu.test.ts +++ b/examples/playwright/src/tests/theia-main-menu.test.ts @@ -14,21 +14,29 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { expect } from '@playwright/test'; -import { OSUtil } from '../util'; +import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; +import { TheiaBrowserAppLoader } from '../theia-app-loader'; import { TheiaMenuBar } from '../theia-main-menu'; -import test, { page } from './fixtures/theia-fixture'; - -let menuBar: TheiaMenuBar; +import { OSUtil } from '../util'; +// the tests in this file reuse a page to run faster and thus are executed serially +test.describe.configure({ mode: 'serial' }); test.describe('Theia Main Menu', () => { - test.beforeAll(async () => { - const app = await TheiaApp.load(page); + let app: TheiaApp; + let menuBar: TheiaMenuBar; + + test.beforeAll(async ({ browser }) => { + const page = await browser.newPage(); + app = await TheiaBrowserAppLoader.load(page); menuBar = app.menuBar; }); + test.afterAll(async () => { + await app.page.close(); + }); + test('should show the main menu bar', async () => { const menuBarItems = await menuBar.visibleMenuBarItems(); expect(menuBarItems).toContain('File'); diff --git a/examples/playwright/src/tests/theia-preference-view.test.ts b/examples/playwright/src/tests/theia-preference-view.test.ts index f6e3491c4bab0..b9fc4d0967430 100644 --- a/examples/playwright/src/tests/theia-preference-view.test.ts +++ b/examples/playwright/src/tests/theia-preference-view.test.ts @@ -14,17 +14,24 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only 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 { DefaultPreferences, PreferenceIds, TheiaPreferenceView } from '../theia-preference-view'; -import test, { page } from './fixtures/theia-fixture'; - -let app: TheiaApp; +// the tests in this file reuse a page to run faster and thus are executed serially +test.describe.configure({ mode: 'serial' }); test.describe('Preference View', () => { - test.beforeAll(async () => { - app = await TheiaApp.load(page); + let app: TheiaApp; + + test.beforeAll(async ({ browser }) => { + const page = await browser.newPage(); + app = await TheiaBrowserAppLoader.load(page); + }); + + test.afterAll(async () => { + await app.page.close(); }); test('should be visible and active after being opened', async () => { diff --git a/examples/playwright/src/tests/theia-problems-view.test.ts b/examples/playwright/src/tests/theia-problems-view.test.ts index 7fc79cc2b118a..48dd8919acfe9 100644 --- a/examples/playwright/src/tests/theia-problems-view.test.ts +++ b/examples/playwright/src/tests/theia-problems-view.test.ts @@ -14,17 +14,24 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only 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 { TheiaProblemsView } from '../theia-problem-view'; -import test, { page } from './fixtures/theia-fixture'; - -let app: TheiaApp; +// the tests in this file reuse a page to run faster and thus are executed serially +test.describe.configure({ mode: 'serial' }); test.describe('Theia Problems View', () => { - test.beforeAll(async () => { - app = await TheiaApp.load(page); + let app: TheiaApp; + + test.beforeAll(async ({ browser }) => { + const page = await browser.newPage(); + app = await TheiaBrowserAppLoader.load(page); + }); + + test.afterAll(async () => { + await app.page.close(); }); test('should be visible and active after being opened', async () => { diff --git a/examples/playwright/src/tests/theia-quick-command.test.ts b/examples/playwright/src/tests/theia-quick-command.test.ts index fcd2d026ad62e..b5392ef0afb3f 100644 --- a/examples/playwright/src/tests/theia-quick-command.test.ts +++ b/examples/playwright/src/tests/theia-quick-command.test.ts @@ -14,25 +14,32 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; +import { TheiaBrowserAppLoader } from '../theia-app-loader'; import { TheiaAboutDialog } from '../theia-about-dialog'; import { TheiaApp } from '../theia-app'; import { TheiaExplorerView } from '../theia-explorer-view'; import { TheiaNotificationIndicator } from '../theia-notification-indicator'; import { TheiaNotificationOverlay } from '../theia-notification-overlay'; import { TheiaQuickCommandPalette } from '../theia-quick-command-palette'; -import test, { page } from './fixtures/theia-fixture'; - -let app: TheiaApp; -let quickCommand: TheiaQuickCommandPalette; +// the tests in this file reuse a page to run faster and thus are executed serially +test.describe.configure({ mode: 'serial' }); test.describe('Theia Quick Command', () => { - test.beforeAll(async () => { - app = await TheiaApp.load(page); + let app: TheiaApp; + let quickCommand: TheiaQuickCommandPalette; + + test.beforeAll(async ({ browser }) => { + const page = await browser.newPage(); + app = await TheiaBrowserAppLoader.load(page); quickCommand = app.quickCommandPalette; }); + test.afterAll(async () => { + await app.page.close(); + }); + test('should show quick command palette', async () => { await quickCommand.open(); expect(await quickCommand.isOpen()).toBe(true); @@ -43,6 +50,7 @@ test.describe('Theia Quick Command', () => { }); test('should trigger \'About\' command after typing', async () => { + await quickCommand.open(); await quickCommand.type('About'); await quickCommand.trigger('About'); expect(await quickCommand.isOpen()).toBe(false); @@ -57,6 +65,7 @@ test.describe('Theia Quick Command', () => { }); test('should trigger \'Toggle Explorer View\' command after typing', async () => { + await quickCommand.open(); await quickCommand.type('Toggle Explorer'); await quickCommand.trigger('Toggle Explorer View'); expect(await quickCommand.isOpen()).toBe(false); diff --git a/examples/playwright/src/tests/theia-sample-app.test.ts b/examples/playwright/src/tests/theia-sample-app.test.ts index 356d80f454662..38456caf2a582 100644 --- a/examples/playwright/src/tests/theia-sample-app.test.ts +++ b/examples/playwright/src/tests/theia-sample-app.test.ts @@ -14,10 +14,11 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only 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'; +import { TheiaWorkspace } from '../theia-workspace'; class TheiaSampleApp extends TheiaApp { protected toolbar = new TheiaToolbar(this); @@ -35,12 +36,19 @@ class TheiaSampleApp extends TheiaApp { } } -let app: TheiaSampleApp; - +// the tests in this file reuse a page to run faster and thus are executed serially +test.describe.configure({ mode: 'serial' }); test.describe('Theia Sample Application', () => { - test('should load', async () => { - app = await TheiaApp.loadApp(page, TheiaSampleApp); + let app: TheiaSampleApp; + + test.beforeAll(async ({ browser }) => { + const page = await browser.newPage(); + app = await TheiaBrowserAppLoader.load(page, new TheiaWorkspace(), TheiaSampleApp); + }); + + test.afterAll(async () => { + await app.page.close(); }); test('should start with visible toolbar', async () => { diff --git a/examples/playwright/src/tests/theia-status-bar.test.ts b/examples/playwright/src/tests/theia-status-bar.test.ts index 20f575ed7cad1..2e1f1b9f90061 100644 --- a/examples/playwright/src/tests/theia-status-bar.test.ts +++ b/examples/playwright/src/tests/theia-status-bar.test.ts @@ -14,23 +14,31 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only 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 { TheiaNotificationIndicator } from '../theia-notification-indicator'; import { TheiaProblemIndicator } from '../theia-problem-indicator'; import { TheiaStatusBar } from '../theia-status-bar'; import { TheiaToggleBottomIndicator } from '../theia-toggle-bottom-indicator'; -import test, { page } from './fixtures/theia-fixture'; - -let statusBar: TheiaStatusBar; +// the tests in this file reuse a page to run faster and thus are executed serially +test.describe.configure({ mode: 'serial' }); test.describe('Theia Status Bar', () => { - test.beforeAll(async () => { - const app = await TheiaApp.load(page); + let app: TheiaApp; + let statusBar: TheiaStatusBar; + + test.beforeAll(async ({ browser }) => { + const page = await browser.newPage(); + app = await TheiaBrowserAppLoader.load(page); statusBar = app.statusBar; }); + test.afterAll(async () => { + await app.page.close(); + }); + test('should show status bar', async () => { expect(await statusBar.isVisible()).toBe(true); }); diff --git a/examples/playwright/src/tests/theia-text-editor.test.ts b/examples/playwright/src/tests/theia-text-editor.test.ts index d47daf09bb129..4789048cd4b24 100644 --- a/examples/playwright/src/tests/theia-text-editor.test.ts +++ b/examples/playwright/src/tests/theia-text-editor.test.ts @@ -14,20 +14,23 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { expect } from '@playwright/test'; -import { DefaultPreferences, PreferenceIds, TheiaPreferenceView } from '../theia-preference-view'; +import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; +import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { DefaultPreferences, PreferenceIds, TheiaPreferenceView } from '../theia-preference-view'; import { TheiaTextEditor } from '../theia-text-editor'; import { TheiaWorkspace } from '../theia-workspace'; -import test, { page } from './fixtures/theia-fixture'; - -let app: TheiaApp; +// the tests in this file reuse a page to run faster and thus are executed serially +test.describe.configure({ mode: 'serial' }); test.describe('Theia Text Editor', () => { - test.beforeAll(async () => { + let app: TheiaApp; + + 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); // set auto-save preference to off const preferenceView = await app.openPreferences(TheiaPreferenceView); @@ -35,6 +38,10 @@ test.describe('Theia Text Editor', () => { await preferenceView.close(); }); + test.afterAll(async () => { + await app.page.close(); + }); + test('should be visible and active after opening "sample.txt"', async () => { const sampleTextEditor = await app.openEditor('sample.txt', TheiaTextEditor); expect(await sampleTextEditor.isTabVisible()).toBe(true); diff --git a/examples/playwright/src/tests/theia-workspace.test.ts b/examples/playwright/src/tests/theia-workspace.test.ts index 02ae0f94c4f9b..e9d4efab5d1f5 100644 --- a/examples/playwright/src/tests/theia-workspace.test.ts +++ b/examples/playwright/src/tests/theia-workspace.test.ts @@ -14,24 +14,23 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { expect } from '@playwright/test'; -import { TheiaApp } from '../theia-app'; +import { expect, test } from '@playwright/test'; +import { TheiaBrowserAppLoader } from '../theia-app-loader'; import { DOT_FILES_FILTER, TheiaExplorerView } from '../theia-explorer-view'; import { TheiaWorkspace } from '../theia-workspace'; -import test, { page } from './fixtures/theia-fixture'; test.describe('Theia Workspace', () => { - test('should be initialized empty by default', async () => { - const app = await TheiaApp.load(page); + test('should be initialized empty by default', async ({ page }) => { + const app = await TheiaBrowserAppLoader.load(page); const explorer = await app.openView(TheiaExplorerView); const fileStatElements = await explorer.visibleFileStatNodes(DOT_FILES_FILTER); expect(fileStatElements.length).toBe(0); }); - test('should be initialized with the contents of a file location', async () => { + test('should be initialized with the contents of a file location', async ({ page }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - const app = await TheiaApp.load(page, ws); + const app = await TheiaBrowserAppLoader.load(page, ws); const explorer = await app.openView(TheiaExplorerView); // resources/sample-files1 contains two folders and one file expect(await explorer.existsDirectoryNode('sampleFolder')).toBe(true); @@ -39,9 +38,9 @@ test.describe('Theia Workspace', () => { expect(await explorer.existsFileNode('sample.txt')).toBe(true); }); - test('should be initialized with the contents of multiple file locations', async () => { + test('should be initialized with the contents of multiple file locations', async ({ page }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1', 'src/tests/resources/sample-files2']); - const app = await TheiaApp.load(page, ws); + const app = await TheiaBrowserAppLoader.load(page, ws); const explorer = await app.openView(TheiaExplorerView); // resources/sample-files1 contains two folders and one file expect(await explorer.existsDirectoryNode('sampleFolder')).toBe(true); diff --git a/examples/playwright/src/theia-app-loader.ts b/examples/playwright/src/theia-app-loader.ts new file mode 100644 index 0000000000000..bea923a8da7bd --- /dev/null +++ b/examples/playwright/src/theia-app-loader.ts @@ -0,0 +1,167 @@ +// ***************************************************************************** +// Copyright (C) 2022 STMicroelectronics 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 { Page, PlaywrightWorkerArgs, _electron as electron } from '@playwright/test'; +import { TheiaApp, TheiaAppMainPageObjects } from './theia-app'; +import { TheiaWorkspace } from './theia-workspace'; + +export interface TheiaAppFactory { + new(page: Page, initialWorkspace?: TheiaWorkspace, mainPageObjects?: TheiaAppMainPageObjects): T; +} + +function theiaAppFactory(factory?: TheiaAppFactory): TheiaAppFactory { + return (factory ?? TheiaApp) as TheiaAppFactory; +} + +function initializeWorkspace(initialWorkspace?: TheiaWorkspace): TheiaWorkspace { + const workspace = initialWorkspace ? initialWorkspace : new TheiaWorkspace(); + workspace.initialize(); + return workspace; +} + +export class TheiaBrowserAppLoader { + + static async load( + page: Page, + initialWorkspace?: TheiaWorkspace, + factory?: TheiaAppFactory + ): Promise { + const workspace = initializeWorkspace(initialWorkspace); + return this.createAndLoad(page, workspace, factory); + } + + protected static async createAndLoad( + page: Page, + workspace: TheiaWorkspace, + factory?: TheiaAppFactory + ): Promise { + const appFactory = theiaAppFactory(factory); + const app = new appFactory(page, workspace); + await this.loadOrReload(app, '/#' + app.workspace.urlEncodedPath); + await app.waitForShellAndInitialized(); + return app; + } + + protected static async loadOrReload(app: TheiaApp, url: string): Promise { + if (app.page.url() === url) { + await app.page.reload(); + } else { + const wasLoadedAlready = await app.isShellVisible(); + await app.page.goto(url); + if (wasLoadedAlready) { + // Theia doesn't refresh on URL change only + // So we need to reload if the app was already loaded before + await app.page.reload(); + } + } + } + +} + +export class TheiaElectronAppLoader { + + static async load( + launchOptions: ElectronLaunchOptions | object, + initialWorkspace?: TheiaWorkspace, + factory?: TheiaAppFactory, + ): Promise { + const workspace = initializeWorkspace(initialWorkspace); + const playwrightOptions = this.toPlaywrightOptions(launchOptions, initialWorkspace); + const electronApp = await electron.launch(playwrightOptions); + const page = await electronApp.firstWindow(); + const appFactory = theiaAppFactory(factory); + const app = new appFactory(page, workspace); + await app.waitForShellAndInitialized(); + return app; + } + + protected static toPlaywrightOptions( + electronLaunchOptions: ElectronLaunchOptions | object, + workspace?: TheiaWorkspace + ): object { + if (electronLaunchOptions instanceof ElectronLaunchOptions) { + return electronLaunchOptions.playwrightOptions(workspace); + } + return electronLaunchOptions; + } +} + +export class ElectronLaunchOptions { + + constructor( + protected readonly electronAppPath: string, + protected readonly pluginsPath?: string, + protected readonly additionalArgs: string[] = ['--no-cluster'] + ) { } + + playwrightOptions(workspace?: TheiaWorkspace): object { + const executablePath = this.electronAppPath + '/node_modules/.bin/electron'; + const args: string[] = []; + args.push(this.electronAppPath); + args.push(...this.additionalArgs); + args.push(`--app-project-path=${this.electronAppPath}`); + if (this.pluginsPath) { + args.push(`--plugins=local-dir:${this.pluginsPath}`); + }; + if (workspace) { + args.push(workspace.path); + } + return { executablePath, args }; + } +} + +// TODO this is just a sketch, we need a proper way to configure tests and pass this configuration to the `TheiaAppLoader`: + +interface TheiaPlaywrightTestConfig { + useElectron?: { + /** Path to the Theia Electron app package (absolute or relative to this package). */ + electronAppPath?: string, + /** Path to the folder containing the plugins to load (absolute or relative to this package). */ + pluginsPath?: string, + // eslint-disable-next-line max-len + /** Electron launch options as [specified by Playwright](https://github.com/microsoft/playwright/blob/396487fc4c19bf27554eac9beea9db135e96cfb4/packages/playwright-core/types/types.d.ts#L14182). */ + launchOptions?: object, + } +} + +export class TheiaAppLoader { + static async load( + args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs, + initialWorkspace?: TheiaWorkspace, + factory?: TheiaAppFactory, + ): Promise { + if (args.useElectron) { + return TheiaAppLoader.launchElectron(args, factory, initialWorkspace); + } + const page = await args.browser.newPage(); + return TheiaBrowserAppLoader.load(page, initialWorkspace, factory); + } + + private static launchElectron( + args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs, + factory?: TheiaAppFactory, + initialWorkspace?: TheiaWorkspace + ): Promise { + const electronConfig = args.useElectron; + if (electronConfig === undefined || electronConfig.launchOptions === undefined && electronConfig.electronAppPath === undefined) { + throw Error('The Theia Playwright configuration must either specify `useElectron.electronAppPath` or `useElectron.launchOptions`'); + } + const appPath = electronConfig.electronAppPath!; + const pluginsPath = electronConfig.pluginsPath; + const launchOptions = electronConfig.launchOptions ?? new ElectronLaunchOptions(appPath, pluginsPath).playwrightOptions(initialWorkspace); + return TheiaElectronAppLoader.load(launchOptions, initialWorkspace, factory); + } +} diff --git a/examples/playwright/src/theia-app.ts b/examples/playwright/src/theia-app.ts index 901a7a172dbc2..2d40079a88a4c 100644 --- a/examples/playwright/src/theia-app.ts +++ b/examples/playwright/src/theia-app.ts @@ -35,49 +35,40 @@ export const DefaultTheiaAppData: TheiaAppData = { shellSelector: '.theia-ApplicationShell' }; +export class TheiaAppMainPageObjects { + statusBar: new (page: TheiaApp) => TheiaStatusBar = TheiaStatusBar; + quickCommandPalette: new (page: TheiaApp) => TheiaQuickCommandPalette = TheiaQuickCommandPalette; + menuBar: new (page: TheiaApp) => TheiaMenuBar = TheiaMenuBar; +} + export class TheiaApp { - readonly statusBar = new TheiaStatusBar(this); - readonly quickCommandPalette = new TheiaQuickCommandPalette(this); - readonly menuBar = new TheiaMenuBar(this); - public workspace: TheiaWorkspace; + statusBar: TheiaStatusBar; + quickCommandPalette: TheiaQuickCommandPalette; + menuBar: TheiaMenuBar; - static async load(page: Page, initialWorkspace?: TheiaWorkspace): Promise { - return this.loadApp(page, TheiaApp, initialWorkspace); - } + protected appData = DefaultTheiaAppData; - static async loadApp(page: Page, appFactory: { new(page: Page, initialWorkspace?: TheiaWorkspace): T }, initialWorkspace?: TheiaWorkspace): Promise { - const app = new appFactory(page, initialWorkspace); - await app.load(); - return app; + public constructor( + public page: Page, + public workspace: TheiaWorkspace, + mainPageObjects: TheiaAppMainPageObjects = new TheiaAppMainPageObjects() + ) { + this.statusBar = new mainPageObjects.statusBar(this); + this.quickCommandPalette = new mainPageObjects.quickCommandPalette(this); + this.menuBar = new mainPageObjects.menuBar(this); } - public constructor(public page: Page, initialWorkspace?: TheiaWorkspace, protected appData = DefaultTheiaAppData) { - this.workspace = initialWorkspace ? initialWorkspace : new TheiaWorkspace(); - this.workspace.initialize(); + async isShellVisible(): Promise { + return this.page.isVisible(this.appData.shellSelector); } - protected async load(): Promise { - await this.loadOrReload(this.page, '/#' + this.workspace.urlEncodedPath); + async waitForShellAndInitialized(): Promise { await this.page.waitForSelector(this.appData.loadingSelector, { state: 'detached' }); await this.page.waitForSelector(this.appData.shellSelector); await this.waitForInitialized(); } - protected async loadOrReload(page: Page, url: string): Promise { - if (page.url() === url) { - await page.reload(); - } else { - const wasLoadedAlready = await page.isVisible(this.appData.shellSelector); - await page.goto(url); - if (wasLoadedAlready) { - // Theia doesn't refresh on URL change only - // So we need to reload if the app was already loaded before - await page.reload(); - } - } - } - async isMainContentPanelVisible(): Promise { const contentPanel = await this.page.$('#theia-main-content-panel'); return !!contentPanel && contentPanel.isVisible(); diff --git a/examples/playwright/src/theia-explorer-view.ts b/examples/playwright/src/theia-explorer-view.ts index 45d66d572a7dc..52c7d9da82626 100644 --- a/examples/playwright/src/theia-explorer-view.ts +++ b/examples/playwright/src/theia-explorer-view.ts @@ -182,6 +182,7 @@ export class TheiaExplorerView extends TheiaView { console.debug('Waiting for clicked tree node to be selected: ' + filePath); } } + await this.page.waitForSelector(this.treeNodeSelector(filePath) + '.theia-mod-selected'); } async isTreeNodeSelected(filePath: string): Promise { From b7c088e512f9981172ab9d60ef4a14d86ec801f0 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Fri, 10 Feb 2023 11:58:54 +0100 Subject: [PATCH 02/19] playwright: improve playwright support for electron 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 --- .../configs/playwright.ci.config.ts | 26 ++++++ .../playwright/configs/playwright.config.ts | 38 ++++++++ .../configs/playwright.debug.config.ts | 27 ++++++ .../configs/playwright.headful.config.ts | 30 ++++++ .../playwright/configs/ui-tests.eslintrc.json | 7 ++ .../configs/ui-tests.playwright.eslintrc.json | 6 ++ examples/playwright/package.json | 5 +- .../playwright/src/tests/theia-app.test.ts | 6 +- .../src/tests/theia-electron-app.test.ts | 91 +++++++++++++++++-- .../src/tests/theia-explorer-view.test.ts | 7 +- .../src/tests/theia-main-menu.test.ts | 7 +- .../src/tests/theia-output-view.test.ts | 12 +-- .../src/tests/theia-preference-view.test.ts | 7 +- .../src/tests/theia-problems-view.test.ts | 7 +- .../src/tests/theia-quick-command.test.ts | 7 +- .../src/tests/theia-sample-app.test.ts | 7 +- .../src/tests/theia-status-bar.test.ts | 7 +- .../src/tests/theia-terminal-view.test.ts | 8 +- .../src/tests/theia-text-editor.test.ts | 7 +- .../src/tests/theia-toolbar.test.ts | 8 +- .../src/tests/theia-workspace.test.ts | 31 +++++-- examples/playwright/src/theia-app-loader.ts | 22 ++++- examples/playwright/src/theia-workspace.ts | 13 +-- .../electron-main-application.ts | 6 +- .../electron-file-dialog-module.ts | 33 ++++++- .../src/electron-browser/preload.ts | 2 + .../src/electron-common/electron-api.ts | 1 + 27 files changed, 341 insertions(+), 87 deletions(-) create mode 100644 examples/playwright/configs/playwright.ci.config.ts create mode 100644 examples/playwright/configs/playwright.config.ts create mode 100644 examples/playwright/configs/playwright.debug.config.ts create mode 100644 examples/playwright/configs/playwright.headful.config.ts create mode 100644 examples/playwright/configs/ui-tests.eslintrc.json create mode 100644 examples/playwright/configs/ui-tests.playwright.eslintrc.json diff --git a/examples/playwright/configs/playwright.ci.config.ts b/examples/playwright/configs/playwright.ci.config.ts new file mode 100644 index 0000000000000..a30b65f7227d0 --- /dev/null +++ b/examples/playwright/configs/playwright.ci.config.ts @@ -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; diff --git a/examples/playwright/configs/playwright.config.ts b/examples/playwright/configs/playwright.config.ts new file mode 100644 index 0000000000000..7cf72757920cc --- /dev/null +++ b/examples/playwright/configs/playwright.config.ts @@ -0,0 +1,38 @@ +// ***************************************************************************** +// 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', + permissions: ['clipboard-read'], + screenshot: 'only-on-failure' + }, + preserveOutput: 'failures-only', + reporter: [ + ['list'], + ['allure-playwright'] + ] +}; + +export default config; diff --git a/examples/playwright/configs/playwright.debug.config.ts b/examples/playwright/configs/playwright.debug.config.ts new file mode 100644 index 0000000000000..41ac56377b9ab --- /dev/null +++ b/examples/playwright/configs/playwright.debug.config.ts @@ -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; diff --git a/examples/playwright/configs/playwright.headful.config.ts b/examples/playwright/configs/playwright.headful.config.ts new file mode 100644 index 0000000000000..8c87926fa2aa8 --- /dev/null +++ b/examples/playwright/configs/playwright.headful.config.ts @@ -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; diff --git a/examples/playwright/configs/ui-tests.eslintrc.json b/examples/playwright/configs/ui-tests.eslintrc.json new file mode 100644 index 0000000000000..735abed1c450a --- /dev/null +++ b/examples/playwright/configs/ui-tests.eslintrc.json @@ -0,0 +1,7 @@ +{ + // override existing rules for ui-tests package + "rules": { + "no-undef": "off", // disabled due to 'browser', '$', '$$' + "no-unused-expressions": "off" + } +} diff --git a/examples/playwright/configs/ui-tests.playwright.eslintrc.json b/examples/playwright/configs/ui-tests.playwright.eslintrc.json new file mode 100644 index 0000000000000..15f12466adae8 --- /dev/null +++ b/examples/playwright/configs/ui-tests.playwright.eslintrc.json @@ -0,0 +1,6 @@ +{ + // override existing rules for ui-tests playwright package + "rules": { + "no-null/no-null": "off" + } +} diff --git a/examples/playwright/package.json b/examples/playwright/package.json index 6797ffc01e125..81eeadfb97e35 100644 --- a/examples/playwright/package.json +++ b/examples/playwright/package.json @@ -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" }, diff --git a/examples/playwright/src/tests/theia-app.test.ts b/examples/playwright/src/tests/theia-app.test.ts index 7316f5afeec3b..eeaed8cafda31 100644 --- a/examples/playwright/src/tests/theia-app.test.ts +++ b/examples/playwright/src/tests/theia-app.test.ts @@ -15,12 +15,12 @@ // ***************************************************************************** import { expect, test } from '@playwright/test'; -import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; test.describe('Theia Application', () => { - test('should load and should show main content panel', async ({ page }) => { - const app = await TheiaBrowserAppLoader.load(page); + test('should load and should show main content panel', async ({ playwright, browser }) => { + const app = await TheiaAppLoader.load({ playwright, browser }); expect(await app.isMainContentPanelVisible()).toBe(true); }); diff --git a/examples/playwright/src/tests/theia-electron-app.test.ts b/examples/playwright/src/tests/theia-electron-app.test.ts index afbdbeb36c2bb..3d5d9e3f27b79 100644 --- a/examples/playwright/src/tests/theia-electron-app.test.ts +++ b/examples/playwright/src/tests/theia-electron-app.test.ts @@ -11,46 +11,117 @@ // 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 +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { expect, test } from '@playwright/test'; import { TheiaExplorerView } from '../theia-explorer-view'; import { TheiaAboutDialog } from '../theia-about-dialog'; -import { ElectronLaunchOptions, TheiaElectronAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaWorkspace } from '../theia-workspace'; +import { TheiaApp } from '../theia-app'; +import { TheiaMenuBar } from 'src/theia-main-menu'; +test.describe.configure({ mode: 'serial' }); test.describe('Theia Electron Application', () => { + let app: TheiaApp; + let ws: TheiaWorkspace; + let menuBar: TheiaMenuBar; + + test.beforeAll(async ({ playwright, browser }) => { + ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); + const args = { + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + }, + playwright: playwright, + browser: browser + + } + ; + app = await TheiaAppLoader.load(args, ws); + menuBar = app.menuBar; + }); + + 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 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 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 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 menuBar.openMenu('View')).clickMenuItem('Explorer'); + const explorerView = new TheiaExplorerView(app); + expect(await explorerView.isDisplayed()).toBe(true); + await (await menuBar.openMenu('View')).clickMenuItem('Explorer'); + expect(await explorerView.isDisplayed()).toBe(false); + }); }); + diff --git a/examples/playwright/src/tests/theia-explorer-view.test.ts b/examples/playwright/src/tests/theia-explorer-view.test.ts index 2cbb43a2d83a5..aec34356e2836 100644 --- a/examples/playwright/src/tests/theia-explorer-view.test.ts +++ b/examples/playwright/src/tests/theia-explorer-view.test.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { expect, test } from '@playwright/test'; -import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaApp } from '../theia-app'; import { DOT_FILES_FILTER, TheiaExplorerView } from '../theia-explorer-view'; import { TheiaWorkspace } from '../theia-workspace'; @@ -27,10 +27,9 @@ test.describe('Theia Explorer View', () => { let app: TheiaApp; let explorer: TheiaExplorerView; - test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); + test.beforeAll(async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - app = await TheiaBrowserAppLoader.load(page, ws); + app = await TheiaAppLoader.load({ playwright, browser }, ws); explorer = await app.openView(TheiaExplorerView); await explorer.waitForVisibleFileNodes(); }); diff --git a/examples/playwright/src/tests/theia-main-menu.test.ts b/examples/playwright/src/tests/theia-main-menu.test.ts index 7e8c78c27f2f5..52e35785ebc75 100644 --- a/examples/playwright/src/tests/theia-main-menu.test.ts +++ b/examples/playwright/src/tests/theia-main-menu.test.ts @@ -16,7 +16,7 @@ import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; -import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaMenuBar } from '../theia-main-menu'; import { OSUtil } from '../util'; @@ -27,9 +27,8 @@ test.describe('Theia Main Menu', () => { let app: TheiaApp; let menuBar: TheiaMenuBar; - test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - app = await TheiaBrowserAppLoader.load(page); + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); menuBar = app.menuBar; }); diff --git a/examples/playwright/src/tests/theia-output-view.test.ts b/examples/playwright/src/tests/theia-output-view.test.ts index 6906ed503be9d..9433bc24d9052 100644 --- a/examples/playwright/src/tests/theia-output-view.test.ts +++ b/examples/playwright/src/tests/theia-output-view.test.ts @@ -14,20 +14,18 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; import { TheiaOutputViewChannel } from '../theia-output-channel'; import { TheiaApp } from '../theia-app'; +import { TheiaAppLoader } 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 ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); }); test('should open the output view and check if is visible and active', async () => { diff --git a/examples/playwright/src/tests/theia-preference-view.test.ts b/examples/playwright/src/tests/theia-preference-view.test.ts index b9fc4d0967430..91c4f1bba67e3 100644 --- a/examples/playwright/src/tests/theia-preference-view.test.ts +++ b/examples/playwright/src/tests/theia-preference-view.test.ts @@ -16,7 +16,7 @@ import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; -import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; import { DefaultPreferences, PreferenceIds, TheiaPreferenceView } from '../theia-preference-view'; // the tests in this file reuse a page to run faster and thus are executed serially @@ -25,9 +25,8 @@ test.describe('Preference View', () => { let app: TheiaApp; - test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - app = await TheiaBrowserAppLoader.load(page); + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-problems-view.test.ts b/examples/playwright/src/tests/theia-problems-view.test.ts index 48dd8919acfe9..11b7a688fb23a 100644 --- a/examples/playwright/src/tests/theia-problems-view.test.ts +++ b/examples/playwright/src/tests/theia-problems-view.test.ts @@ -16,7 +16,7 @@ import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; -import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaProblemsView } from '../theia-problem-view'; // the tests in this file reuse a page to run faster and thus are executed serially @@ -25,9 +25,8 @@ test.describe('Theia Problems View', () => { let app: TheiaApp; - test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - app = await TheiaBrowserAppLoader.load(page); + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-quick-command.test.ts b/examples/playwright/src/tests/theia-quick-command.test.ts index b5392ef0afb3f..10478432be14f 100644 --- a/examples/playwright/src/tests/theia-quick-command.test.ts +++ b/examples/playwright/src/tests/theia-quick-command.test.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { expect, test } from '@playwright/test'; -import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaAboutDialog } from '../theia-about-dialog'; import { TheiaApp } from '../theia-app'; import { TheiaExplorerView } from '../theia-explorer-view'; @@ -30,9 +30,8 @@ test.describe('Theia Quick Command', () => { let app: TheiaApp; let quickCommand: TheiaQuickCommandPalette; - test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - app = await TheiaBrowserAppLoader.load(page); + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); quickCommand = app.quickCommandPalette; }); diff --git a/examples/playwright/src/tests/theia-sample-app.test.ts b/examples/playwright/src/tests/theia-sample-app.test.ts index 38456caf2a582..d9afb7e1de4bf 100644 --- a/examples/playwright/src/tests/theia-sample-app.test.ts +++ b/examples/playwright/src/tests/theia-sample-app.test.ts @@ -16,7 +16,7 @@ import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; -import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaToolbar } from '../theia-toolbar'; import { TheiaWorkspace } from '../theia-workspace'; @@ -42,9 +42,8 @@ test.describe('Theia Sample Application', () => { let app: TheiaSampleApp; - test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - app = await TheiaBrowserAppLoader.load(page, new TheiaWorkspace(), TheiaSampleApp); + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }, new TheiaWorkspace(), TheiaSampleApp); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-status-bar.test.ts b/examples/playwright/src/tests/theia-status-bar.test.ts index 2e1f1b9f90061..f9bb801640499 100644 --- a/examples/playwright/src/tests/theia-status-bar.test.ts +++ b/examples/playwright/src/tests/theia-status-bar.test.ts @@ -16,7 +16,7 @@ import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; -import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaNotificationIndicator } from '../theia-notification-indicator'; import { TheiaProblemIndicator } from '../theia-problem-indicator'; import { TheiaStatusBar } from '../theia-status-bar'; @@ -29,9 +29,8 @@ test.describe('Theia Status Bar', () => { let app: TheiaApp; let statusBar: TheiaStatusBar; - test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - app = await TheiaBrowserAppLoader.load(page); + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); statusBar = app.statusBar; }); diff --git a/examples/playwright/src/tests/theia-terminal-view.test.ts b/examples/playwright/src/tests/theia-terminal-view.test.ts index 807e35a3c9e15..403a2586d1fc2 100644 --- a/examples/playwright/src/tests/theia-terminal-view.test.ts +++ b/examples/playwright/src/tests/theia-terminal-view.test.ts @@ -14,19 +14,19 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; +import { TheiaAppLoader } 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 ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - app = await TheiaApp.load(page, ws); + app = await TheiaAppLoader.load({ playwright, browser }, ws); }); test('should be possible to open a new terminal', async () => { diff --git a/examples/playwright/src/tests/theia-text-editor.test.ts b/examples/playwright/src/tests/theia-text-editor.test.ts index 4789048cd4b24..1227e05782633 100644 --- a/examples/playwright/src/tests/theia-text-editor.test.ts +++ b/examples/playwright/src/tests/theia-text-editor.test.ts @@ -16,7 +16,7 @@ import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; -import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; import { DefaultPreferences, PreferenceIds, TheiaPreferenceView } from '../theia-preference-view'; import { TheiaTextEditor } from '../theia-text-editor'; import { TheiaWorkspace } from '../theia-workspace'; @@ -27,10 +27,9 @@ test.describe('Theia Text Editor', () => { let app: TheiaApp; - test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); + test.beforeAll(async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - app = await TheiaBrowserAppLoader.load(page, ws); + app = await TheiaAppLoader.load({ playwright, browser }, ws); // set auto-save preference to off const preferenceView = await app.openPreferences(TheiaPreferenceView); diff --git a/examples/playwright/src/tests/theia-toolbar.test.ts b/examples/playwright/src/tests/theia-toolbar.test.ts index 1d5234c09208d..9091612c4d7d4 100644 --- a/examples/playwright/src/tests/theia-toolbar.test.ts +++ b/examples/playwright/src/tests/theia-toolbar.test.ts @@ -14,18 +14,18 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; +import { TheiaAppLoader } 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 ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); toolbar = new TheiaToolbar(app); }); diff --git a/examples/playwright/src/tests/theia-workspace.test.ts b/examples/playwright/src/tests/theia-workspace.test.ts index e9d4efab5d1f5..c6df6e72cc191 100644 --- a/examples/playwright/src/tests/theia-workspace.test.ts +++ b/examples/playwright/src/tests/theia-workspace.test.ts @@ -15,22 +15,22 @@ // ***************************************************************************** import { expect, test } from '@playwright/test'; -import { TheiaBrowserAppLoader } from '../theia-app-loader'; +import { TheiaAppLoader } from '../theia-app-loader'; import { DOT_FILES_FILTER, TheiaExplorerView } from '../theia-explorer-view'; import { TheiaWorkspace } from '../theia-workspace'; test.describe('Theia Workspace', () => { - test('should be initialized empty by default', async ({ page }) => { - const app = await TheiaBrowserAppLoader.load(page); + test('should be initialized empty by default', async ({ playwright, browser }) => { + const app = await TheiaAppLoader.load({ playwright, browser }); const explorer = await app.openView(TheiaExplorerView); const fileStatElements = await explorer.visibleFileStatNodes(DOT_FILES_FILTER); expect(fileStatElements.length).toBe(0); }); - test('should be initialized with the contents of a file location', async ({ page }) => { + test('should be initialized with the contents of a file location', async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - const app = await TheiaBrowserAppLoader.load(page, ws); + const app = await TheiaAppLoader.load({ playwright, browser }, ws); const explorer = await app.openView(TheiaExplorerView); // resources/sample-files1 contains two folders and one file expect(await explorer.existsDirectoryNode('sampleFolder')).toBe(true); @@ -38,9 +38,9 @@ test.describe('Theia Workspace', () => { expect(await explorer.existsFileNode('sample.txt')).toBe(true); }); - test('should be initialized with the contents of multiple file locations', async ({ page }) => { + test('should be initialized with the contents of multiple file locations', async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1', 'src/tests/resources/sample-files2']); - const app = await TheiaBrowserAppLoader.load(page, ws); + const app = await TheiaAppLoader.load({ playwright, browser }, ws); const explorer = await app.openView(TheiaExplorerView); // resources/sample-files1 contains two folders and one file expect(await explorer.existsDirectoryNode('sampleFolder')).toBe(true); @@ -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 ({ playwright, browser }) => { + const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); + const app = await TheiaAppLoader.load({ playwright, browser }, 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); + }); + }); diff --git a/examples/playwright/src/theia-app-loader.ts b/examples/playwright/src/theia-app-loader.ts index bea923a8da7bd..674ba4ac812c7 100644 --- a/examples/playwright/src/theia-app-loader.ts +++ b/examples/playwright/src/theia-app-loader.ts @@ -11,12 +11,15 @@ // 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 +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** 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 { new(page: Page, initialWorkspace?: TheiaWorkspace, mainPageObjects?: TheiaAppMainPageObjects): T; @@ -32,7 +35,7 @@ function initializeWorkspace(initialWorkspace?: TheiaWorkspace): TheiaWorkspace return workspace; } -export class TheiaBrowserAppLoader { +class TheiaBrowserAppLoader { static async load( page: Page, @@ -71,7 +74,7 @@ export class TheiaBrowserAppLoader { } -export class TheiaElectronAppLoader { +class TheiaElectronAppLoader { static async load( launchOptions: ElectronLaunchOptions | object, @@ -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); @@ -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 }; } } diff --git a/examples/playwright/src/theia-workspace.ts b/examples/playwright/src/theia-workspace.ts index f7385bca5f84f..ab1f9c391ef17 100644 --- a/examples/playwright/src/theia-workspace.ts +++ b/examples/playwright/src/theia-workspace.ts @@ -15,6 +15,7 @@ // ***************************************************************************** import * as fs from 'fs-extra'; +import * as path from 'path'; import { resolve } from 'path'; import { OSUtil, urlEncodePath } from './util'; @@ -29,7 +30,7 @@ export class TheiaWorkspace { * @param {string[]} pathOfFilesToInitialize Path to files or folders that shall be copied to the workspace */ constructor(protected pathOfFilesToInitialize?: string[]) { - this.workspacePath = fs.mkdtempSync(`${OSUtil.tmpDir}${OSUtil.fileSeparator}cloud-ws-`); + this.workspacePath = fs.mkdtempSync(path.join(OSUtil.tmpDir, 'cloud-ws-')); } /** Performs the file system operations preparing the workspace location synchronously. */ @@ -46,15 +47,7 @@ export class TheiaWorkspace { } get path(): string { - let workspacePath = this.workspacePath; - if (!OSUtil.osStartsWithFileSeparator(this.workspacePath)) { - workspacePath = `${OSUtil.fileSeparator}${workspacePath}`; - } - if (OSUtil.isWindows) { - // Drive letters in windows paths have to be lower case - workspacePath = workspacePath.replace(/.:/, matchedChar => matchedChar.toLowerCase()); - } - return workspacePath; + return path.normalize(this.workspacePath); } get urlEncodedPath(): string { diff --git a/packages/core/src/electron-main/electron-main-application.ts b/packages/core/src/electron-main/electron-main-application.ts index c1394d5d06d6e..64d6244e80bfb 100644 --- a/packages/core/src/electron-main/electron-main-application.ts +++ b/packages/core/src/electron-main/electron-main-application.ts @@ -212,6 +212,7 @@ export class ElectronMainApplication { } async start(config: FrontendApplicationConfig): Promise { + const args = this.processArgv.getProcessArgvWithoutBin(process.argv); this.useNativeWindowFrame = this.getTitleBarStyle(config) === 'native'; this._config = config; this.hookApplicationEvents(); @@ -223,12 +224,15 @@ export class ElectronMainApplication { await this.startContributions(); await this.launch({ secondInstance: false, - argv: this.processArgv.getProcessArgvWithoutBin(process.argv), + argv: args, cwd: process.cwd() }); } protected getTitleBarStyle(config: FrontendApplicationConfig): 'native' | 'custom' { + if ('THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS' in process.env && process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS === '1') { + return 'custom'; + } if (isOSX) { return 'native'; } diff --git a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts index 94a2381944296..bc1bbe28f962b 100644 --- a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts +++ b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts @@ -15,10 +15,37 @@ // ***************************************************************************** import { ContainerModule } from '@theia/core/shared/inversify'; -import { FileDialogService } from '../../browser/file-dialog/file-dialog-service'; +import { DefaultFileDialogService, FileDialogService } from '../../browser/file-dialog/file-dialog-service'; import { ElectronFileDialogService } from './electron-file-dialog-service'; +import { LocationListRenderer, LocationListRendererFactory, LocationListRendererOptions } from '../../browser/location'; +import { FileDialogHiddenFilesToggleRenderer, HiddenFilesToggleRendererFactory } from '../../browser/file-dialog/file-dialog-hidden-files-renderer'; +import { FileDialogTree } from '../../browser/file-dialog/file-dialog-tree'; +import { FileDialogTreeFiltersRenderer, FileDialogTreeFiltersRendererFactory, FileDialogTreeFiltersRendererOptions } from '../../browser/file-dialog'; export default new ContainerModule((bind, _unbind, _isBound, rebind) => { - bind(ElectronFileDialogService).toSelf().inSingletonScope(); - rebind(FileDialogService).toService(ElectronFileDialogService); + if (window.electronTheiaFilesystem.useNativeDialogs) { + bind(ElectronFileDialogService).toSelf().inSingletonScope(); + rebind(FileDialogService).toService(ElectronFileDialogService); + } else { + bind(DefaultFileDialogService).toSelf().inSingletonScope(); + bind(FileDialogService).toService(DefaultFileDialogService); + bind(LocationListRendererFactory).toFactory(context => (options: LocationListRendererOptions) => { + const childContainer = context.container.createChild(); + childContainer.bind(LocationListRendererOptions).toConstantValue(options); + childContainer.bind(LocationListRenderer).toSelf().inSingletonScope(); + return childContainer.get(LocationListRenderer); + }); + bind(FileDialogTreeFiltersRendererFactory).toFactory(context => (options: FileDialogTreeFiltersRendererOptions) => { + const childContainer = context.container.createChild(); + childContainer.bind(FileDialogTreeFiltersRendererOptions).toConstantValue(options); + childContainer.bind(FileDialogTreeFiltersRenderer).toSelf().inSingletonScope(); + return childContainer.get(FileDialogTreeFiltersRenderer); + }); + bind(HiddenFilesToggleRendererFactory).toFactory(({ container }) => (fileDialogTree: FileDialogTree) => { + const child = container.createChild(); + child.bind(FileDialogTree).toConstantValue(fileDialogTree); + child.bind(FileDialogHiddenFilesToggleRenderer).toSelf().inSingletonScope(); + return child.get(FileDialogHiddenFilesToggleRenderer); + }); + } }); diff --git a/packages/filesystem/src/electron-browser/preload.ts b/packages/filesystem/src/electron-browser/preload.ts index 7a5f0d3ffd400..64cb7f79c6af9 100644 --- a/packages/filesystem/src/electron-browser/preload.ts +++ b/packages/filesystem/src/electron-browser/preload.ts @@ -21,10 +21,12 @@ import { ipcRenderer, contextBridge } from '@theia/core/electron-shared/electron const api: TheiaFilesystemAPI = { showOpenDialog: (options: OpenDialogOptions) => ipcRenderer.invoke(CHANNEL_SHOW_OPEN, options), showSaveDialog: (options: SaveDialogOptions) => ipcRenderer.invoke(CHANNEL_SHOW_SAVE, options), + useNativeDialogs: !('THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS' in process.env && process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS === '1') }; export function preload(): void { console.log('exposing theia filesystem electron api'); contextBridge.exposeInMainWorld('electronTheiaFilesystem', api); + } diff --git a/packages/filesystem/src/electron-common/electron-api.ts b/packages/filesystem/src/electron-common/electron-api.ts index 59d59d3a14403..5145f308dfced 100644 --- a/packages/filesystem/src/electron-common/electron-api.ts +++ b/packages/filesystem/src/electron-common/electron-api.ts @@ -43,6 +43,7 @@ export interface SaveDialogOptions { export interface TheiaFilesystemAPI { showOpenDialog(options: OpenDialogOptions): Promise; showSaveDialog(options: SaveDialogOptions): Promise; + useNativeDialogs: boolean; } declare global { From b7c72f9d4bedb8a6fa56d1b123925a129f77dec1 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Thu, 7 Sep 2023 16:38:41 +0200 Subject: [PATCH 03/19] playwright: make all tests executable in electron * Use unified AppLoader for both electron and browser tests * Control early window loading of electron via environment variable THEIA_ELECTRON_NO_EARLY_WINDOW * Introduce environment variable USE_ELECTRON to run playwright tests in electron * Add ui-tests-electron target to package.json * Adjust workflow to use the new ui-tests-ci target (only one worker + both electron and browser tests) * Merge electron playwright tests into the existing tests * Skip tests that are currently too flaky in electron: - theia-output-view.test.ts - theia-quick-command.test.ts Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- .github/workflows/playwright.yml | 11 +- .../configs/playwright.ci.config.ts | 3 +- .../playwright/configs/playwright.config.ts | 10 +- examples/playwright/package.json | 3 +- examples/playwright/playwright.config.ts | 51 ------- .../playwright/src/tests/theia-app.test.ts | 24 +++- .../src/tests/theia-electron-app.test.ts | 127 ----------------- .../src/tests/theia-explorer-view.test.ts | 40 +++++- .../src/tests/theia-main-menu.test.ts | 40 +++++- .../src/tests/theia-output-view.test.ts | 134 +++++++++++------- .../src/tests/theia-preference-view.test.ts | 18 ++- .../src/tests/theia-problems-view.test.ts | 18 ++- .../src/tests/theia-quick-command.test.ts | 121 +++++++++------- .../src/tests/theia-sample-app.test.ts | 18 ++- .../src/tests/theia-status-bar.test.ts | 18 ++- .../src/tests/theia-terminal-view.test.ts | 22 ++- .../src/tests/theia-text-editor.test.ts | 18 ++- .../src/tests/theia-toolbar.test.ts | 22 ++- .../src/tests/theia-workspace.test.ts | 48 +++++-- examples/playwright/src/theia-app-loader.ts | 3 +- .../playwright/src/theia-preference-view.ts | 8 ++ .../electron-main-application.ts | 14 +- 22 files changed, 457 insertions(+), 314 deletions(-) delete mode 100644 examples/playwright/playwright.config.ts delete mode 100644 examples/playwright/src/tests/theia-electron-app.test.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 0473f62bb2726..f91bd3926824d 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -43,6 +43,15 @@ jobs: NODE_OPTIONS: --max_old_space_size=4096 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # https://github.com/microsoft/vscode-ripgrep/issues/9 + - name: Build Electron + shell: bash + run: | + yarn --skip-integrity-check --network-timeout 100000 + yarn electron build + env: + NODE_OPTIONS: --max_old_space_size=4096 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # https://github.com/microsoft/vscode-ripgrep/issues/9 + - name: Build Playwright shell: bash run: | @@ -51,4 +60,4 @@ jobs: - name: Test (playwright) uses: GabrielBB/xvfb-action@v1 with: - run: yarn test:playwright + run: yarn --cwd examples/playwright ui-tests-ci diff --git a/examples/playwright/configs/playwright.ci.config.ts b/examples/playwright/configs/playwright.ci.config.ts index a30b65f7227d0..4fa758b32a3c9 100644 --- a/examples/playwright/configs/playwright.ci.config.ts +++ b/examples/playwright/configs/playwright.ci.config.ts @@ -20,7 +20,8 @@ import baseConfig from './playwright.config'; const ciConfig: PlaywrightTestConfig = { ...baseConfig, workers: 1, - retries: 1 + retries: 2, + reporter: [['list'], ['allure-playwright'], ['github']] }; export default ciConfig; diff --git a/examples/playwright/configs/playwright.config.ts b/examples/playwright/configs/playwright.config.ts index 7cf72757920cc..ee0e56dd775c9 100644 --- a/examples/playwright/configs/playwright.config.ts +++ b/examples/playwright/configs/playwright.config.ts @@ -19,7 +19,7 @@ import { PlaywrightTestConfig } from '@playwright/test'; const config: PlaywrightTestConfig = { testDir: '../lib/tests', testMatch: ['**/*.js'], - workers: 2, + workers: 1, // Timeout for each test in milliseconds. timeout: 60 * 1000, use: { @@ -32,7 +32,13 @@ const config: PlaywrightTestConfig = { reporter: [ ['list'], ['allure-playwright'] - ] + ], + // Reuse Theia backend on port 3000 or start instance before executing the tests + webServer: { + command: 'yarn theia:start', + port: 3000, + reuseExistingServer: true + } }; export default config; diff --git a/examples/playwright/package.json b/examples/playwright/package.json index 81eeadfb97e35..61a3ca6c57b16 100644 --- a/examples/playwright/package.json +++ b/examples/playwright/package.json @@ -20,7 +20,8 @@ "lint:fix": "eslint -c ./.eslintrc.js --ext .ts ./src --fix", "playwright:install": "playwright install chromium", "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-electron": "yarn build && USE_ELECTRON=true playwright test --config=./configs/playwright.config.ts", + "ui-tests-ci": "yarn build && playwright test --config=./configs/playwright.ci.config.ts && USE_ELECTRON=true 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" diff --git a/examples/playwright/playwright.config.ts b/examples/playwright/playwright.config.ts deleted file mode 100644 index f07dd808d837f..0000000000000 --- a/examples/playwright/playwright.config.ts +++ /dev/null @@ -1,51 +0,0 @@ -// ***************************************************************************** -// Copyright (C) 2021-2023 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-only WITH Classpath-exception-2.0 -// ***************************************************************************** - -import { defineConfig } from '@playwright/test'; - -// Note: process.env.CI is always set to true for GitHub Actions - -export default defineConfig({ - testDir: './lib/tests', - testMatch: ['**/*.test.js'], - workers: process.env.CI ? 1 : 2, - retries: process.env.CI ? 1 : 0, - // The number of times to repeat each test, useful for debugging flaky tests - repeatEach: 1, - // Timeout for each test in milliseconds. - timeout: 30 * 1000, - use: { - baseURL: 'http://localhost:3000', - browserName: 'chromium', - screenshot: 'only-on-failure', - permissions: ['clipboard-read'], - viewport: { width: 1920, height: 1080 }, - }, - snapshotDir: './src/tests/snapshots', - expect: { - toMatchSnapshot: { threshold: 0.01 } - }, - preserveOutput: 'failures-only', - reporter: process.env.CI - ? [['list'], ['allure-playwright'], ['github']] - : [['list'], ['allure-playwright']], - // Reuse Theia backend on port 3000 or start instance before executing the tests - webServer: { - command: 'yarn theia:start', - port: 3000, - reuseExistingServer: true - } -}); diff --git a/examples/playwright/src/tests/theia-app.test.ts b/examples/playwright/src/tests/theia-app.test.ts index eeaed8cafda31..4316cda657e48 100644 --- a/examples/playwright/src/tests/theia-app.test.ts +++ b/examples/playwright/src/tests/theia-app.test.ts @@ -16,11 +16,33 @@ import { expect, test } from '@playwright/test'; import { TheiaAppLoader } from '../theia-app-loader'; +import { TheiaApp } from '../theia-app'; test.describe('Theia Application', () => { + let app: TheiaApp; + + test.afterAll(async () => { + await app.page.close(); + }); test('should load and should show main content panel', async ({ playwright, browser }) => { - const app = await TheiaAppLoader.load({ playwright, browser }); + let args; + if (process.env.USE_ELECTRON === 'true') { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args); expect(await app.isMainContentPanelVisible()).toBe(true); }); diff --git a/examples/playwright/src/tests/theia-electron-app.test.ts b/examples/playwright/src/tests/theia-electron-app.test.ts deleted file mode 100644 index 3d5d9e3f27b79..0000000000000 --- a/examples/playwright/src/tests/theia-electron-app.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -// ***************************************************************************** -// Copyright (C) 2022 STMicroelectronics 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-only WITH Classpath-exception-2.0 -// ***************************************************************************** - -import { expect, test } from '@playwright/test'; -import { TheiaExplorerView } from '../theia-explorer-view'; -import { TheiaAboutDialog } from '../theia-about-dialog'; -import { TheiaAppLoader } from '../theia-app-loader'; -import { TheiaWorkspace } from '../theia-workspace'; -import { TheiaApp } from '../theia-app'; -import { TheiaMenuBar } from 'src/theia-main-menu'; - -test.describe.configure({ mode: 'serial' }); -test.describe('Theia Electron Application', () => { - - let app: TheiaApp; - let ws: TheiaWorkspace; - let menuBar: TheiaMenuBar; - - test.beforeAll(async ({ playwright, browser }) => { - ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - const args = { - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - }, - playwright: playwright, - browser: browser - - } - ; - app = await TheiaAppLoader.load(args, ws); - menuBar = app.menuBar; - }); - - test.afterAll(async () => { - await app.page.close(); - }); - - test('should load and show main content panel', async () => { - expect(await app.isMainContentPanelVisible()).toBe(true); - }); - - test('open about dialog using menu', async () => { - await (await 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); - }); - - test('open file via file menu and cancel', async () => { - await (await 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 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'); - 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); - }); - - 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'); - 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 menuBar.openMenu('View')).clickMenuItem('Explorer'); - const explorerView = new TheiaExplorerView(app); - expect(await explorerView.isDisplayed()).toBe(true); - await (await menuBar.openMenu('View')).clickMenuItem('Explorer'); - expect(await explorerView.isDisplayed()).toBe(false); - }); -}); - diff --git a/examples/playwright/src/tests/theia-explorer-view.test.ts b/examples/playwright/src/tests/theia-explorer-view.test.ts index aec34356e2836..490fbd808d7a0 100644 --- a/examples/playwright/src/tests/theia-explorer-view.test.ts +++ b/examples/playwright/src/tests/theia-explorer-view.test.ts @@ -17,6 +17,7 @@ import { expect, test } from '@playwright/test'; import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaApp } from '../theia-app'; +import { PreferenceIds, TheiaPreferenceView } from '../theia-preference-view'; import { DOT_FILES_FILTER, TheiaExplorerView } from '../theia-explorer-view'; import { TheiaWorkspace } from '../theia-workspace'; @@ -26,10 +27,36 @@ test.describe('Theia Explorer View', () => { let app: TheiaApp; let explorer: TheiaExplorerView; + let isElectron: boolean; test.beforeAll(async ({ playwright, browser }) => { + isElectron = process.env.USE_ELECTRON === 'true'; const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - app = await TheiaAppLoader.load({ playwright, browser }, ws); + let args; + if (isElectron) { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args, ws); + + if (isElectron) { + // set trash preference to off + const preferenceView = await app.openPreferences(TheiaPreferenceView); + await preferenceView.setBooleanPreferenceById(PreferenceIds.Files.EnableTrash, false); + await preferenceView.close(); + } + explorer = await app.openView(TheiaExplorerView); await explorer.waitForVisibleFileNodes(); }); @@ -137,7 +164,9 @@ test.describe('Theia Explorer View', () => { const menuItems = await menu.visibleMenuItems(); expect(menuItems).toContain('Open'); expect(menuItems).toContain('Delete'); - expect(menuItems).toContain('Download'); + if (!isElectron) { + expect(menuItems).toContain('Download'); + } await menu.close(); expect(await menu.isOpen()).toBe(false); @@ -192,4 +221,11 @@ test.describe('Theia Explorer View', () => { expect(updatedFileStatElements.length).toBe(fileStatElements.length - 1); }); + test('open "sample.txt" via the context menu', async () => { + expect(await explorer.existsFileNode('sample.txt')).toBe(true); + await explorer.clickContextMenuItem('sample.txt', ['Open']); + const span = await app.page.waitForSelector('span:has-text("content line 2")'); + expect(await span.isVisible()).toBe(true); + }); + }); diff --git a/examples/playwright/src/tests/theia-main-menu.test.ts b/examples/playwright/src/tests/theia-main-menu.test.ts index 52e35785ebc75..c8d3d24d47904 100644 --- a/examples/playwright/src/tests/theia-main-menu.test.ts +++ b/examples/playwright/src/tests/theia-main-menu.test.ts @@ -17,6 +17,7 @@ import { expect, test } from '@playwright/test'; import { TheiaApp } from '../theia-app'; import { TheiaAppLoader } from '../theia-app-loader'; +import { TheiaAboutDialog } from '../theia-about-dialog'; import { TheiaMenuBar } from '../theia-main-menu'; import { OSUtil } from '../util'; @@ -26,9 +27,27 @@ test.describe('Theia Main Menu', () => { let app: TheiaApp; let menuBar: TheiaMenuBar; + let isElectron: boolean; test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({ playwright, browser }); + isElectron = process.env.USE_ELECTRON === 'true'; + let args; + if (isElectron) { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args); menuBar = app.menuBar; }); @@ -64,7 +83,7 @@ test.describe('Theia Main Menu', () => { expect(label).toBe('New Text File'); const shortCut = await menuItem?.shortCut(); - expect(shortCut).toBe(OSUtil.isMacOS ? '⌥ N' : 'Alt+N'); + expect(shortCut).toBe(OSUtil.isMacOS ? '⌥ N' : isElectron ? 'Ctrl+N' : 'Alt+N'); const hasSubmenu = await menuItem?.hasSubmenu(); expect(hasSubmenu).toBe(false); @@ -93,4 +112,21 @@ test.describe('Theia Main Menu', () => { expect(await mainMenu.isOpen()).toBe(false); }); + test('open about dialog using menu', async () => { + await (await 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); + }); + + test('open file via file menu and cancel', async () => { + const openFileEntry = isElectron ? 'Open File...' : 'Open...'; + await (await menuBar.openMenu('File')).clickMenuItem(openFileEntry); + const fileDialog = await app.page.waitForSelector('div[class="dialogBlock"]'); + expect(await fileDialog.isVisible()).toBe(true); + await app.page.locator('#theia-dialog-shell').getByRole('button', { name: 'Cancel' }).click(); + expect(await fileDialog.isVisible()).toBe(false); + }); + }); diff --git a/examples/playwright/src/tests/theia-output-view.test.ts b/examples/playwright/src/tests/theia-output-view.test.ts index 9433bc24d9052..65d824ef8d023 100644 --- a/examples/playwright/src/tests/theia-output-view.test.ts +++ b/examples/playwright/src/tests/theia-output-view.test.ts @@ -20,64 +20,88 @@ import { TheiaApp } from '../theia-app'; import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaOutputView } from '../theia-output-view'; -let app: TheiaApp; let outputView: TheiaOutputView; let testChannel: TheiaOutputViewChannel; +if (process.env.USE_ELECTRON === 'true') { + // TODO: remove this once the test is stable enough with electron + test.describe.skip('Theia Output View', () => {}); +} else { + let app: TheiaApp; let outputView: TheiaOutputView; let testChannel: TheiaOutputViewChannel; + test.describe('Theia Output View', () => { -test.describe('Theia Output View', () => { + test.beforeAll(async ({ playwright, browser }) => { + let args; + if (process.env.USE_ELECTRON === 'true') { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args); + }); - test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({ playwright, browser }); - }); + test.afterAll(async () => { + await app.page.close(); + }); - test('should open the output view and check if is visible and active', async () => { - outputView = await app.openView(TheiaOutputView); - expect(await outputView.isTabVisible()).toBe(true); - expect(await outputView.isDisplayed()).toBe(true); - expect(await outputView.isActive()).toBe(true); - }); - test('should be opened at the bottom and have the title "Output"', async () => { - expect(await outputView.isInSidePanel()).toBe(false); - expect(await outputView.side()).toBe('bottom'); - expect(await outputView.title()).toBe('Output'); - }); - test('should be closable', async () => { - expect(await outputView.isClosable()).toBe(true); - await outputView.close(); - expect(await outputView.isTabVisible()).toBe(false); - expect(await outputView.isDisplayed()).toBe(false); - expect(await outputView.isActive()).toBe(false); - }); - test('should select a test output channel', async () => { - outputView = await app.openView(TheiaOutputView); - expect(await outputView.isTabVisible()).toBe(true); - expect(await outputView.isDisplayed()).toBe(true); - expect(await outputView.isActive()).toBe(true); + test('should open the output view and check if is visible and active', async () => { + outputView = await app.openView(TheiaOutputView); + expect(await outputView.isTabVisible()).toBe(true); + expect(await outputView.isDisplayed()).toBe(true); + expect(await outputView.isActive()).toBe(true); + }); + test('should be opened at the bottom and have the title "Output"', async () => { + expect(await outputView.isInSidePanel()).toBe(false); + expect(await outputView.side()).toBe('bottom'); + expect(await outputView.title()).toBe('Output'); + }); + test('should be closable', async () => { + expect(await outputView.isClosable()).toBe(true); + await outputView.close(); + expect(await outputView.isTabVisible()).toBe(false); + expect(await outputView.isDisplayed()).toBe(false); + expect(await outputView.isActive()).toBe(false); + }); + test('should select a test output channel', async () => { + outputView = await app.openView(TheiaOutputView); + expect(await outputView.isTabVisible()).toBe(true); + expect(await outputView.isDisplayed()).toBe(true); + expect(await outputView.isActive()).toBe(true); - const testChannelName = 'API Sample: my test channel'; - expect(await outputView.selectOutputChannel(testChannelName)).toBe(true); - }); - test('should check if the output view of the test output channel', async () => { - const testChannelName = 'API Sample: my test channel'; - expect(await outputView.isOutputChannelSelected(testChannelName)); - const channel = await outputView.getOutputChannel(testChannelName); - expect(channel).toBeDefined; - testChannel = channel!; - expect(await testChannel!.isDisplayed()).toBe(true); - }); - test('should check if the output view test channel shows the test output', async () => { - expect(await testChannel.numberOfLines()).toBe(5); - expect(await testChannel.textContentOfLineByLineNumber(1)).toMatch('hello info1'); - expect(await testChannel.maxSeverityOfLineByLineNumber(1)).toMatch('info'); - expect(await testChannel.textContentOfLineByLineNumber(2)).toMatch('hello info2'); - expect(await testChannel.maxSeverityOfLineByLineNumber(2)).toMatch('info'); - expect(await testChannel.textContentOfLineByLineNumber(3)).toMatch('hello error'); - expect(await testChannel.maxSeverityOfLineByLineNumber(3)).toMatch('error'); - expect(await testChannel.textContentOfLineByLineNumber(4)).toMatch('hello warning'); - expect(await testChannel.maxSeverityOfLineByLineNumber(4)).toMatch('warning'); - expect(await testChannel.textContentOfLineByLineNumber(5)).toMatch( - 'inlineInfo1 inlineWarning inlineError inlineInfo2' - ); - expect(await testChannel.maxSeverityOfLineByLineNumber(5)).toMatch('error'); - }); + const testChannelName = 'API Sample: my test channel'; + expect(await outputView.selectOutputChannel(testChannelName)).toBe(true); + }); + test('should check if the output view of the test output channel', async () => { + const testChannelName = 'API Sample: my test channel'; + expect(await outputView.isOutputChannelSelected(testChannelName)); + const channel = await outputView.getOutputChannel(testChannelName); + expect(channel).toBeDefined; + testChannel = channel!; + expect(await testChannel!.isDisplayed()).toBe(true); + }); + test('should check if the output view test channel shows the test output', async () => { + expect(await testChannel.numberOfLines()).toBe(5); + expect(await testChannel.textContentOfLineByLineNumber(1)).toMatch('hello info1'); + expect(await testChannel.maxSeverityOfLineByLineNumber(1)).toMatch('info'); + expect(await testChannel.textContentOfLineByLineNumber(2)).toMatch('hello info2'); + expect(await testChannel.maxSeverityOfLineByLineNumber(2)).toMatch('info'); + expect(await testChannel.textContentOfLineByLineNumber(3)).toMatch('hello error'); + expect(await testChannel.maxSeverityOfLineByLineNumber(3)).toMatch('error'); + expect(await testChannel.textContentOfLineByLineNumber(4)).toMatch('hello warning'); + expect(await testChannel.maxSeverityOfLineByLineNumber(4)).toMatch('warning'); + expect(await testChannel.textContentOfLineByLineNumber(5)).toMatch( + 'inlineInfo1 inlineWarning inlineError inlineInfo2' + ); + expect(await testChannel.maxSeverityOfLineByLineNumber(5)).toMatch('error'); + }); -}); + }); +} diff --git a/examples/playwright/src/tests/theia-preference-view.test.ts b/examples/playwright/src/tests/theia-preference-view.test.ts index 91c4f1bba67e3..83b980f58cbb3 100644 --- a/examples/playwright/src/tests/theia-preference-view.test.ts +++ b/examples/playwright/src/tests/theia-preference-view.test.ts @@ -26,7 +26,23 @@ test.describe('Preference View', () => { let app: TheiaApp; test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({ playwright, browser }); + let args; + if (process.env.USE_ELECTRON === 'true') { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-problems-view.test.ts b/examples/playwright/src/tests/theia-problems-view.test.ts index 11b7a688fb23a..cd544504edb22 100644 --- a/examples/playwright/src/tests/theia-problems-view.test.ts +++ b/examples/playwright/src/tests/theia-problems-view.test.ts @@ -26,7 +26,23 @@ test.describe('Theia Problems View', () => { let app: TheiaApp; test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({ playwright, browser }); + let args; + if (process.env.USE_ELECTRON === 'true') { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-quick-command.test.ts b/examples/playwright/src/tests/theia-quick-command.test.ts index 10478432be14f..cd8ec06fcd67b 100644 --- a/examples/playwright/src/tests/theia-quick-command.test.ts +++ b/examples/playwright/src/tests/theia-quick-command.test.ts @@ -23,62 +23,83 @@ import { TheiaNotificationIndicator } from '../theia-notification-indicator'; import { TheiaNotificationOverlay } from '../theia-notification-overlay'; import { TheiaQuickCommandPalette } from '../theia-quick-command-palette'; -// the tests in this file reuse a page to run faster and thus are executed serially -test.describe.configure({ mode: 'serial' }); -test.describe('Theia Quick Command', () => { +if (process.env.USE_ELECTRON === 'true') { + // TODO: remove this once the test is stable enough with electron + test.describe.skip('Theia Quick Command', () => {}); +} else { + // the tests in this file reuse a page to run faster and thus are executed serially + test.describe.configure({ mode: 'serial' }); + test.describe('Theia Quick Command', () => { - let app: TheiaApp; - let quickCommand: TheiaQuickCommandPalette; + let app: TheiaApp; + let quickCommand: TheiaQuickCommandPalette; - test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({ playwright, browser }); - quickCommand = app.quickCommandPalette; - }); + test.beforeAll(async ({ playwright, browser }) => { + let args; + if (process.env.USE_ELECTRON === 'true') { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args); + quickCommand = app.quickCommandPalette; + }); - test.afterAll(async () => { - await app.page.close(); - }); + test.afterAll(async () => { + await app.page.close(); + }); - test('should show quick command palette', async () => { - await quickCommand.open(); - expect(await quickCommand.isOpen()).toBe(true); - await quickCommand.hide(); - expect(await quickCommand.isOpen()).toBe(false); - await quickCommand.open(); - expect(await quickCommand.isOpen()).toBe(true); - }); + test('should show quick command palette', async () => { + await quickCommand.open(); + expect(await quickCommand.isOpen()).toBe(true); + await quickCommand.hide(); + expect(await quickCommand.isOpen()).toBe(false); + await quickCommand.open(); + expect(await quickCommand.isOpen()).toBe(true); + }); - test('should trigger \'About\' command after typing', async () => { - 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(); - expect(await aboutDialog.isVisible()).toBe(false); + test('should trigger \'About\' command after typing', async () => { + 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(); + expect(await aboutDialog.isVisible()).toBe(false); - await quickCommand.type('Select All'); - await quickCommand.trigger('Select All'); - expect(await quickCommand.isOpen()).toBe(false); - }); + await quickCommand.type('Select All'); + await quickCommand.trigger('Select All'); + expect(await quickCommand.isOpen()).toBe(false); + }); - test('should trigger \'Toggle Explorer View\' command after typing', async () => { - 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); - }); + test('should trigger \'Toggle Explorer View\' command after typing', async () => { + 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); + }); - test('should trigger \'Quick Input: Test Positive Integer\' command by confirming via Enter', async () => { - await quickCommand.type('Test Positive', true); - expect(await quickCommand.isOpen()).toBe(true); - await quickCommand.type('6', true); - const notificationIndicator = new TheiaNotificationIndicator(app); - const notification = new TheiaNotificationOverlay(app, notificationIndicator); - expect(await notification.isEntryVisible('Positive Integer: 6')).toBe(true); - }); + test('should trigger \'Quick Input: Test Positive Integer\' command by confirming via Enter', async () => { + await quickCommand.type('Test Positive', true); + expect(await quickCommand.isOpen()).toBe(true); + await quickCommand.type('6', true); + const notificationIndicator = new TheiaNotificationIndicator(app); + const notification = new TheiaNotificationOverlay(app, notificationIndicator); + expect(await notification.isEntryVisible('Positive Integer: 6')).toBe(true); + }); -}); + }); +} diff --git a/examples/playwright/src/tests/theia-sample-app.test.ts b/examples/playwright/src/tests/theia-sample-app.test.ts index d9afb7e1de4bf..56b283b20df44 100644 --- a/examples/playwright/src/tests/theia-sample-app.test.ts +++ b/examples/playwright/src/tests/theia-sample-app.test.ts @@ -43,7 +43,23 @@ test.describe('Theia Sample Application', () => { let app: TheiaSampleApp; test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({ playwright, browser }, new TheiaWorkspace(), TheiaSampleApp); + let args; + if (process.env.USE_ELECTRON === 'true') { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args, new TheiaWorkspace(), TheiaSampleApp); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-status-bar.test.ts b/examples/playwright/src/tests/theia-status-bar.test.ts index f9bb801640499..603b28b193e28 100644 --- a/examples/playwright/src/tests/theia-status-bar.test.ts +++ b/examples/playwright/src/tests/theia-status-bar.test.ts @@ -30,7 +30,23 @@ test.describe('Theia Status Bar', () => { let statusBar: TheiaStatusBar; test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({ playwright, browser }); + let args; + if (process.env.USE_ELECTRON === 'true') { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args); statusBar = app.statusBar; }); diff --git a/examples/playwright/src/tests/theia-terminal-view.test.ts b/examples/playwright/src/tests/theia-terminal-view.test.ts index 403a2586d1fc2..ce6d4749a2854 100644 --- a/examples/playwright/src/tests/theia-terminal-view.test.ts +++ b/examples/playwright/src/tests/theia-terminal-view.test.ts @@ -26,7 +26,27 @@ test.describe('Theia Terminal View', () => { test.beforeAll(async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - app = await TheiaAppLoader.load({ playwright, browser }, ws); + let args; + if (process.env.USE_ELECTRON === 'true') { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args, ws); + }); + + test.afterAll(async () => { + await app.page.close(); }); test('should be possible to open a new terminal', async () => { diff --git a/examples/playwright/src/tests/theia-text-editor.test.ts b/examples/playwright/src/tests/theia-text-editor.test.ts index 1227e05782633..050daa2633dd4 100644 --- a/examples/playwright/src/tests/theia-text-editor.test.ts +++ b/examples/playwright/src/tests/theia-text-editor.test.ts @@ -29,7 +29,23 @@ test.describe('Theia Text Editor', () => { test.beforeAll(async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - app = await TheiaAppLoader.load({ playwright, browser }, ws); + let args; + if (process.env.USE_ELECTRON === 'true') { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args, ws); // set auto-save preference to off const preferenceView = await app.openPreferences(TheiaPreferenceView); diff --git a/examples/playwright/src/tests/theia-toolbar.test.ts b/examples/playwright/src/tests/theia-toolbar.test.ts index 9091612c4d7d4..0bad63a9779eb 100644 --- a/examples/playwright/src/tests/theia-toolbar.test.ts +++ b/examples/playwright/src/tests/theia-toolbar.test.ts @@ -25,10 +25,30 @@ let toolbar: TheiaToolbar; test.describe('Theia Toolbar', () => { test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({ playwright, browser }); + let args; + if (process.env.USE_ELECTRON === 'true') { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + app = await TheiaAppLoader.load(args); toolbar = new TheiaToolbar(app); }); + test.afterAll(async () => { + await app.page.close(); + }); + test('should toggle the toolbar and check visibility', async () => { // depending on the user settings we have different starting conditions for the toolbar const isShownInitially = await toolbar.isShown(); diff --git a/examples/playwright/src/tests/theia-workspace.test.ts b/examples/playwright/src/tests/theia-workspace.test.ts index c6df6e72cc191..0c6924a593e24 100644 --- a/examples/playwright/src/tests/theia-workspace.test.ts +++ b/examples/playwright/src/tests/theia-workspace.test.ts @@ -14,33 +14,57 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { expect, test } from '@playwright/test'; -import { TheiaAppLoader } from '../theia-app-loader'; +import { PlaywrightWorkerArgs, expect, test } from '@playwright/test'; +import { TheiaAppLoader, TheiaPlaywrightTestConfig } from '../theia-app-loader'; import { DOT_FILES_FILTER, TheiaExplorerView } from '../theia-explorer-view'; import { TheiaWorkspace } from '../theia-workspace'; test.describe('Theia Workspace', () => { + let args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs; + let isElectron: boolean; + test.beforeAll(async ({ playwright, browser }) => { + isElectron = process.env.USE_ELECTRON === 'true'; + if (isElectron) { + args = { + playwright: playwright, + browser: browser, + useElectron: { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + } + }; + } else { + args = { + playwright: playwright, + browser: browser + }; + } + }); test('should be initialized empty by default', async ({ playwright, browser }) => { - const app = await TheiaAppLoader.load({ playwright, browser }); - const explorer = await app.openView(TheiaExplorerView); - const fileStatElements = await explorer.visibleFileStatNodes(DOT_FILES_FILTER); - expect(fileStatElements.length).toBe(0); + if (!isElectron) { + const app = await TheiaAppLoader.load(args); + const explorer = await app.openView(TheiaExplorerView); + const fileStatElements = await explorer.visibleFileStatNodes(DOT_FILES_FILTER); + expect(fileStatElements.length).toBe(0); + await app.page.close(); + } }); test('should be initialized with the contents of a file location', async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - const app = await TheiaAppLoader.load({ playwright, browser }, ws); + const app = await TheiaAppLoader.load(args, ws); const explorer = await app.openView(TheiaExplorerView); // resources/sample-files1 contains two folders and one file expect(await explorer.existsDirectoryNode('sampleFolder')).toBe(true); expect(await explorer.existsDirectoryNode('sampleFolderCompact')).toBe(true); expect(await explorer.existsFileNode('sample.txt')).toBe(true); + await app.page.close(); }); test('should be initialized with the contents of multiple file locations', async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1', 'src/tests/resources/sample-files2']); - const app = await TheiaAppLoader.load({ playwright, browser }, ws); + const app = await TheiaAppLoader.load(args, ws); const explorer = await app.openView(TheiaExplorerView); // resources/sample-files1 contains two folders and one file expect(await explorer.existsDirectoryNode('sampleFolder')).toBe(true); @@ -48,12 +72,13 @@ test.describe('Theia Workspace', () => { expect(await explorer.existsFileNode('sample.txt')).toBe(true); // resources/sample-files2 contains one file expect(await explorer.existsFileNode('another-sample.txt')).toBe(true); + await app.page.close(); }); test('open sample.txt via file menu', async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - const app = await TheiaAppLoader.load({ playwright, browser }, ws); - const menuEntry = 'Open...'; + const app = await TheiaAppLoader.load(args, ws); + const menuEntry = isElectron ? 'Open File...' : 'Open...'; await (await app.menuBar.openMenu('File')).clickMenuItem(menuEntry); const fileDialog = await app.page.waitForSelector('div[class="dialogBlock"]'); @@ -61,10 +86,11 @@ test.describe('Theia Workspace', () => { const fileEntry = app.page.getByText('sample.txt'); await fileEntry.click(); - await app.page.getByRole('button', { name: 'Open' }).click(); + await app.page.locator('#theia-dialog-shell').getByRole('button', { name: 'Open' }).click(); const span = await app.page.waitForSelector('span:has-text("content line 2")'); expect(await span.isVisible()).toBe(true); + await app.page.close(); }); }); diff --git a/examples/playwright/src/theia-app-loader.ts b/examples/playwright/src/theia-app-loader.ts index 674ba4ac812c7..8ce639e699ecc 100644 --- a/examples/playwright/src/theia-app-loader.ts +++ b/examples/playwright/src/theia-app-loader.ts @@ -132,6 +132,7 @@ export class ElectronLaunchOptions { args.push(workspace.path); } process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS = '1'; + process.env.THEIA_ELECTRON_NO_EARLY_WINDOW = '1'; console.log(`Launching Electron: ${executablePath} ${args.join(' ')}`); return { executablePath, args }; } @@ -139,7 +140,7 @@ export class ElectronLaunchOptions { // TODO this is just a sketch, we need a proper way to configure tests and pass this configuration to the `TheiaAppLoader`: -interface TheiaPlaywrightTestConfig { +export interface TheiaPlaywrightTestConfig { useElectron?: { /** Path to the Theia Electron app package (absolute or relative to this package). */ electronAppPath?: string, diff --git a/examples/playwright/src/theia-preference-view.ts b/examples/playwright/src/theia-preference-view.ts index a32a7c56e23a4..a1f701048b17b 100644 --- a/examples/playwright/src/theia-preference-view.ts +++ b/examples/playwright/src/theia-preference-view.ts @@ -33,6 +33,9 @@ export const PreferenceIds = { }, DiffEditor: { MaxComputationTime: 'diffEditor.maxComputationTime' + }, + Files: { + EnableTrash: 'files.enableTrash' } }; @@ -59,6 +62,11 @@ export const DefaultPreferences = { }, DiffEditor: { MaxComputationTime: '5000' + }, + Files: { + EnableTrash: { + Enabled: true + } } }; diff --git a/packages/core/src/electron-main/electron-main-application.ts b/packages/core/src/electron-main/electron-main-application.ts index 64d6244e80bfb..988df268218c6 100644 --- a/packages/core/src/electron-main/electron-main-application.ts +++ b/packages/core/src/electron-main/electron-main-application.ts @@ -264,7 +264,15 @@ export class ElectronMainApplication { if (browserWindow) { this.saveWindowState(browserWindow); } else { - console.warn(`no BrowserWindow with id: ${webContents.id}`); + console.warn(`no BrowserWindow wit "theia": { + "frontend": { + "config": { + "electron": { + "showWindowEarly": false + } + } + } + },h id: ${webContents.id}`); } } @@ -277,7 +285,9 @@ export class ElectronMainApplication { } protected showInitialWindow(): void { - if (this.config.electron.showWindowEarly) { + if (this.config.electron.showWindowEarly && + !('THEIA_ELECTRON_NO_EARLY_WINDOW' in process.env && process.env.THEIA_ELECTRON_NO_EARLY_WINDOW === '1')) { + console.log('Showing main window early'); app.whenReady().then(async () => { const options = await this.getLastWindowOptions(); this.initialWindow = await this.createWindow({ ...options }); From eead58388524faba48ab013d162c36db4421b54f Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 23 Oct 2023 13:34:07 +0200 Subject: [PATCH 04/19] fix: workspace loading in theia-electron-app-loader Ensures that the workspace is defined before electron launches. Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- examples/playwright/src/theia-app-loader.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/playwright/src/theia-app-loader.ts b/examples/playwright/src/theia-app-loader.ts index 8ce639e699ecc..58120085ad7fc 100644 --- a/examples/playwright/src/theia-app-loader.ts +++ b/examples/playwright/src/theia-app-loader.ts @@ -82,7 +82,7 @@ class TheiaElectronAppLoader { factory?: TheiaAppFactory, ): Promise { const workspace = initializeWorkspace(initialWorkspace); - const playwrightOptions = this.toPlaywrightOptions(launchOptions, initialWorkspace); + const playwrightOptions = this.toPlaywrightOptions(launchOptions, workspace); const electronApp = await electron.launch(playwrightOptions); const page = await electronApp.firstWindow(); const appFactory = theiaAppFactory(factory); @@ -176,7 +176,7 @@ export class TheiaAppLoader { } const appPath = electronConfig.electronAppPath!; const pluginsPath = electronConfig.pluginsPath; - const launchOptions = electronConfig.launchOptions ?? new ElectronLaunchOptions(appPath, pluginsPath).playwrightOptions(initialWorkspace); + const launchOptions = electronConfig.launchOptions ?? new ElectronLaunchOptions(appPath, pluginsPath); return TheiaElectronAppLoader.load(launchOptions, initialWorkspace, factory); } } From 162bb8b9d1e656dc20f950e217fb2dd1bd919aca Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 30 Oct 2023 11:32:04 +0100 Subject: [PATCH 05/19] fix: query useNativeDialogs in implementation not in bindings Instead of using useNativeDialogs() to change bindings, we use to decide whether to call the extended FileDialogService or the parent implementation. This should simplify debugging and help moving away from the environment variable approach towards preferences or something else. Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- .../electron-file-dialog-module.ts | 46 +++++++-------- .../electron-file-dialog-service.ts | 56 ++++++++++--------- 2 files changed, 52 insertions(+), 50 deletions(-) diff --git a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts index bc1bbe28f962b..94bc8a0193624 100644 --- a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts +++ b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts @@ -23,29 +23,25 @@ import { FileDialogTree } from '../../browser/file-dialog/file-dialog-tree'; import { FileDialogTreeFiltersRenderer, FileDialogTreeFiltersRendererFactory, FileDialogTreeFiltersRendererOptions } from '../../browser/file-dialog'; export default new ContainerModule((bind, _unbind, _isBound, rebind) => { - if (window.electronTheiaFilesystem.useNativeDialogs) { - bind(ElectronFileDialogService).toSelf().inSingletonScope(); - rebind(FileDialogService).toService(ElectronFileDialogService); - } else { - bind(DefaultFileDialogService).toSelf().inSingletonScope(); - bind(FileDialogService).toService(DefaultFileDialogService); - bind(LocationListRendererFactory).toFactory(context => (options: LocationListRendererOptions) => { - const childContainer = context.container.createChild(); - childContainer.bind(LocationListRendererOptions).toConstantValue(options); - childContainer.bind(LocationListRenderer).toSelf().inSingletonScope(); - return childContainer.get(LocationListRenderer); - }); - bind(FileDialogTreeFiltersRendererFactory).toFactory(context => (options: FileDialogTreeFiltersRendererOptions) => { - const childContainer = context.container.createChild(); - childContainer.bind(FileDialogTreeFiltersRendererOptions).toConstantValue(options); - childContainer.bind(FileDialogTreeFiltersRenderer).toSelf().inSingletonScope(); - return childContainer.get(FileDialogTreeFiltersRenderer); - }); - bind(HiddenFilesToggleRendererFactory).toFactory(({ container }) => (fileDialogTree: FileDialogTree) => { - const child = container.createChild(); - child.bind(FileDialogTree).toConstantValue(fileDialogTree); - child.bind(FileDialogHiddenFilesToggleRenderer).toSelf().inSingletonScope(); - return child.get(FileDialogHiddenFilesToggleRenderer); - }); - } + bind(DefaultFileDialogService).toSelf().inSingletonScope(); + bind(LocationListRendererFactory).toFactory(context => (options: LocationListRendererOptions) => { + const childContainer = context.container.createChild(); + childContainer.bind(LocationListRendererOptions).toConstantValue(options); + childContainer.bind(LocationListRenderer).toSelf().inSingletonScope(); + return childContainer.get(LocationListRenderer); + }); + bind(FileDialogTreeFiltersRendererFactory).toFactory(context => (options: FileDialogTreeFiltersRendererOptions) => { + const childContainer = context.container.createChild(); + childContainer.bind(FileDialogTreeFiltersRendererOptions).toConstantValue(options); + childContainer.bind(FileDialogTreeFiltersRenderer).toSelf().inSingletonScope(); + return childContainer.get(FileDialogTreeFiltersRenderer); + }); + bind(HiddenFilesToggleRendererFactory).toFactory(({ container }) => (fileDialogTree: FileDialogTree) => { + const child = container.createChild(); + child.bind(FileDialogTree).toConstantValue(fileDialogTree); + child.bind(FileDialogHiddenFilesToggleRenderer).toSelf().inSingletonScope(); + return child.get(FileDialogHiddenFilesToggleRenderer); + }); + bind(ElectronFileDialogService).toSelf().inSingletonScope(); + rebind(FileDialogService).toService(ElectronFileDialogService); }); diff --git a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts index 8b3c03d3dbe29..52f2d64cfac53 100644 --- a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts +++ b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts @@ -39,40 +39,46 @@ export class ElectronFileDialogService extends DefaultFileDialogService { override async showOpenDialog(props: OpenFileDialogProps & { canSelectMany: true }, folder?: FileStat): Promise | undefined>; override async showOpenDialog(props: OpenFileDialogProps, folder?: FileStat): Promise; override async showOpenDialog(props: OpenFileDialogProps, folder?: FileStat): Promise | undefined> { - const rootNode = await this.getRootNode(folder); - if (rootNode) { - const filePaths = await window.electronTheiaFilesystem.showOpenDialog(this.toOpenDialogOptions(rootNode.uri, props)); - if (!filePaths || filePaths.length === 0) { - return undefined; - } + if (window.electronTheiaFilesystem.useNativeDialogs) { + const rootNode = await this.getRootNode(folder); + if (rootNode) { + const filePaths = await window.electronTheiaFilesystem.showOpenDialog(this.toOpenDialogOptions(rootNode.uri, props)); + if (!filePaths || filePaths.length === 0) { + return undefined; + } - const uris = filePaths.map(path => FileUri.create(path)); - const canAccess = await this.canRead(uris); - const result = canAccess ? uris.length === 1 ? uris[0] : uris : undefined; - return result; + const uris = filePaths.map(path => FileUri.create(path)); + const canAccess = await this.canRead(uris); + const result = canAccess ? uris.length === 1 ? uris[0] : uris : undefined; + return result; + } + return undefined; } - return undefined; + return super.showOpenDialog(props, folder); } override async showSaveDialog(props: SaveFileDialogProps, folder?: FileStat): Promise { - const rootNode = await this.getRootNode(folder); - if (rootNode) { - const filePath = await window.electronTheiaFilesystem.showSaveDialog(this.toSaveDialogOptions(rootNode.uri, props)); + if (window.electronTheiaFilesystem.useNativeDialogs) { + const rootNode = await this.getRootNode(folder); + if (rootNode) { + const filePath = await window.electronTheiaFilesystem.showSaveDialog(this.toSaveDialogOptions(rootNode.uri, props)); - if (!filePath) { - return undefined; - } + if (!filePath) { + return undefined; + } - const uri = FileUri.create(filePath); - const exists = await this.fileService.exists(uri); - if (!exists) { - return uri; - } + const uri = FileUri.create(filePath); + const exists = await this.fileService.exists(uri); + if (!exists) { + return uri; + } - const canWrite = await this.canReadWrite(uri); - return canWrite ? uri : undefined; + const canWrite = await this.canReadWrite(uri); + return canWrite ? uri : undefined; + } + return undefined; } - return undefined; + return super.showSaveDialog(props, folder); } protected async canReadWrite(uris: MaybeArray): Promise { From 199f814e78371a0c22bda4238c1195eab967a1e1 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 30 Oct 2023 11:57:26 +0100 Subject: [PATCH 06/19] fix: Remove duplicated bindings Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- .../electron-file-dialog-module.ts | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts index 94bc8a0193624..8756b35ca6464 100644 --- a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts +++ b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-module.ts @@ -15,33 +15,10 @@ // ***************************************************************************** import { ContainerModule } from '@theia/core/shared/inversify'; -import { DefaultFileDialogService, FileDialogService } from '../../browser/file-dialog/file-dialog-service'; import { ElectronFileDialogService } from './electron-file-dialog-service'; -import { LocationListRenderer, LocationListRendererFactory, LocationListRendererOptions } from '../../browser/location'; -import { FileDialogHiddenFilesToggleRenderer, HiddenFilesToggleRendererFactory } from '../../browser/file-dialog/file-dialog-hidden-files-renderer'; -import { FileDialogTree } from '../../browser/file-dialog/file-dialog-tree'; -import { FileDialogTreeFiltersRenderer, FileDialogTreeFiltersRendererFactory, FileDialogTreeFiltersRendererOptions } from '../../browser/file-dialog'; +import { FileDialogService } from '../../browser'; export default new ContainerModule((bind, _unbind, _isBound, rebind) => { - bind(DefaultFileDialogService).toSelf().inSingletonScope(); - bind(LocationListRendererFactory).toFactory(context => (options: LocationListRendererOptions) => { - const childContainer = context.container.createChild(); - childContainer.bind(LocationListRendererOptions).toConstantValue(options); - childContainer.bind(LocationListRenderer).toSelf().inSingletonScope(); - return childContainer.get(LocationListRenderer); - }); - bind(FileDialogTreeFiltersRendererFactory).toFactory(context => (options: FileDialogTreeFiltersRendererOptions) => { - const childContainer = context.container.createChild(); - childContainer.bind(FileDialogTreeFiltersRendererOptions).toConstantValue(options); - childContainer.bind(FileDialogTreeFiltersRenderer).toSelf().inSingletonScope(); - return childContainer.get(FileDialogTreeFiltersRenderer); - }); - bind(HiddenFilesToggleRendererFactory).toFactory(({ container }) => (fileDialogTree: FileDialogTree) => { - const child = container.createChild(); - child.bind(FileDialogTree).toConstantValue(fileDialogTree); - child.bind(FileDialogHiddenFilesToggleRenderer).toSelf().inSingletonScope(); - return child.get(FileDialogHiddenFilesToggleRenderer); - }); bind(ElectronFileDialogService).toSelf().inSingletonScope(); rebind(FileDialogService).toService(ElectronFileDialogService); }); From a2d7297cddb4a0b5a937a54912383ad6e772f294 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 30 Oct 2023 12:26:12 +0100 Subject: [PATCH 07/19] chore: move useNativeDialogs to core + rename to useNativeElements This generalized flag applies to all electron-based UI and should therefore go to core. Also, the name is more general now. Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- packages/core/src/electron-browser/preload.ts | 1 + packages/core/src/electron-common/electron-api.ts | 1 + .../file-dialog/electron-file-dialog-service.ts | 6 ++++-- packages/filesystem/src/electron-browser/preload.ts | 1 - packages/filesystem/src/electron-common/electron-api.ts | 1 - 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/core/src/electron-browser/preload.ts b/packages/core/src/electron-browser/preload.ts index b9c05e337f34b..1ce37cc52b633 100644 --- a/packages/core/src/electron-browser/preload.ts +++ b/packages/core/src/electron-browser/preload.ts @@ -207,6 +207,7 @@ const api: TheiaCoreAPI = { sendData: data => { ipcRenderer.send(CHANNEL_IPC_CONNECTION, data); }, + useNativeElements: !('THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS' in process.env && process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS === '1') }; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/core/src/electron-common/electron-api.ts b/packages/core/src/electron-common/electron-api.ts index 64fa5625a309c..9007a41e747e5 100644 --- a/packages/core/src/electron-common/electron-api.ts +++ b/packages/core/src/electron-common/electron-api.ts @@ -87,6 +87,7 @@ export interface TheiaCoreAPI { sendData(data: Uint8Array): void; onData(handler: (data: Uint8Array) => void): Disposable; + useNativeElements: boolean; } declare global { diff --git a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts index 52f2d64cfac53..c3edec5afd967 100644 --- a/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts +++ b/packages/filesystem/src/electron-browser/file-dialog/electron-file-dialog-service.ts @@ -31,6 +31,8 @@ import { DefaultFileDialogService, OpenFileDialogProps, SaveFileDialogProps } fr import { FileUri } from '@theia/core/lib/node/file-uri'; import { OpenDialogOptions, SaveDialogOptions } from '../../electron-common/electron-api'; +import '@theia/core/lib/electron-common/electron-api'; + @injectable() export class ElectronFileDialogService extends DefaultFileDialogService { @@ -39,7 +41,7 @@ export class ElectronFileDialogService extends DefaultFileDialogService { override async showOpenDialog(props: OpenFileDialogProps & { canSelectMany: true }, folder?: FileStat): Promise | undefined>; override async showOpenDialog(props: OpenFileDialogProps, folder?: FileStat): Promise; override async showOpenDialog(props: OpenFileDialogProps, folder?: FileStat): Promise | undefined> { - if (window.electronTheiaFilesystem.useNativeDialogs) { + if (window.electronTheiaCore.useNativeElements) { const rootNode = await this.getRootNode(folder); if (rootNode) { const filePaths = await window.electronTheiaFilesystem.showOpenDialog(this.toOpenDialogOptions(rootNode.uri, props)); @@ -58,7 +60,7 @@ export class ElectronFileDialogService extends DefaultFileDialogService { } override async showSaveDialog(props: SaveFileDialogProps, folder?: FileStat): Promise { - if (window.electronTheiaFilesystem.useNativeDialogs) { + if (window.electronTheiaCore.useNativeElements) { const rootNode = await this.getRootNode(folder); if (rootNode) { const filePath = await window.electronTheiaFilesystem.showSaveDialog(this.toSaveDialogOptions(rootNode.uri, props)); diff --git a/packages/filesystem/src/electron-browser/preload.ts b/packages/filesystem/src/electron-browser/preload.ts index 64cb7f79c6af9..32c2290c6120f 100644 --- a/packages/filesystem/src/electron-browser/preload.ts +++ b/packages/filesystem/src/electron-browser/preload.ts @@ -21,7 +21,6 @@ import { ipcRenderer, contextBridge } from '@theia/core/electron-shared/electron const api: TheiaFilesystemAPI = { showOpenDialog: (options: OpenDialogOptions) => ipcRenderer.invoke(CHANNEL_SHOW_OPEN, options), showSaveDialog: (options: SaveDialogOptions) => ipcRenderer.invoke(CHANNEL_SHOW_SAVE, options), - useNativeDialogs: !('THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS' in process.env && process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS === '1') }; export function preload(): void { diff --git a/packages/filesystem/src/electron-common/electron-api.ts b/packages/filesystem/src/electron-common/electron-api.ts index 5145f308dfced..59d59d3a14403 100644 --- a/packages/filesystem/src/electron-common/electron-api.ts +++ b/packages/filesystem/src/electron-common/electron-api.ts @@ -43,7 +43,6 @@ export interface SaveDialogOptions { export interface TheiaFilesystemAPI { showOpenDialog(options: OpenDialogOptions): Promise; showSaveDialog(options: SaveDialogOptions): Promise; - useNativeDialogs: boolean; } declare global { From 1536956555d51497a081211abd33d4e732f35411 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 30 Oct 2023 12:41:32 +0100 Subject: [PATCH 08/19] chore: revert corrupted warning message Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- .../src/electron-main/electron-main-application.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/core/src/electron-main/electron-main-application.ts b/packages/core/src/electron-main/electron-main-application.ts index 988df268218c6..3a67acd71ff1b 100644 --- a/packages/core/src/electron-main/electron-main-application.ts +++ b/packages/core/src/electron-main/electron-main-application.ts @@ -264,15 +264,7 @@ export class ElectronMainApplication { if (browserWindow) { this.saveWindowState(browserWindow); } else { - console.warn(`no BrowserWindow wit "theia": { - "frontend": { - "config": { - "electron": { - "showWindowEarly": false - } - } - } - },h id: ${webContents.id}`); + console.warn(`no BrowserWindow with id: ${webContents.id}`); } } From 625d699cb8b1a6b08e7bb50991402806c2ae4ea7 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 30 Oct 2023 13:06:59 +0100 Subject: [PATCH 09/19] chore: refactor TheiaAppLoader Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- examples/playwright/src/theia-app-loader.ts | 32 ++++++++------------- examples/playwright/src/theia-app.ts | 1 + 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/examples/playwright/src/theia-app-loader.ts b/examples/playwright/src/theia-app-loader.ts index 58120085ad7fc..73e8dd42081a8 100644 --- a/examples/playwright/src/theia-app-loader.ts +++ b/examples/playwright/src/theia-app-loader.ts @@ -22,7 +22,7 @@ import { TheiaWorkspace } from './theia-workspace'; import { OSUtil } from './util'; export interface TheiaAppFactory { - new(page: Page, initialWorkspace?: TheiaWorkspace, mainPageObjects?: TheiaAppMainPageObjects): T; + new(page: Page, initialWorkspace?: TheiaWorkspace, isElectron?: boolean, mainPageObjects?: TheiaAppMainPageObjects): T; } function theiaAppFactory(factory?: TheiaAppFactory): TheiaAppFactory { @@ -52,7 +52,7 @@ class TheiaBrowserAppLoader { factory?: TheiaAppFactory ): Promise { const appFactory = theiaAppFactory(factory); - const app = new appFactory(page, workspace); + const app = new appFactory(page, workspace, false); await this.loadOrReload(app, '/#' + app.workspace.urlEncodedPath); await app.waitForShellAndInitialized(); return app; @@ -77,16 +77,23 @@ class TheiaBrowserAppLoader { class TheiaElectronAppLoader { static async load( - launchOptions: ElectronLaunchOptions | object, + args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs, initialWorkspace?: TheiaWorkspace, factory?: TheiaAppFactory, ): Promise { const workspace = initializeWorkspace(initialWorkspace); + const electronConfig = args.useElectron; + if (electronConfig === undefined || electronConfig.launchOptions === undefined && electronConfig.electronAppPath === undefined) { + throw Error('The Theia Playwright configuration must either specify `useElectron.electronAppPath` or `useElectron.launchOptions`'); + } + const appPath = electronConfig.electronAppPath!; + const pluginsPath = electronConfig.pluginsPath; + const launchOptions = electronConfig.launchOptions ?? new ElectronLaunchOptions(appPath, pluginsPath); const playwrightOptions = this.toPlaywrightOptions(launchOptions, workspace); const electronApp = await electron.launch(playwrightOptions); const page = await electronApp.firstWindow(); const appFactory = theiaAppFactory(factory); - const app = new appFactory(page, workspace); + const app = new appFactory(page, workspace, true); await app.waitForShellAndInitialized(); return app; } @@ -159,24 +166,9 @@ export class TheiaAppLoader { factory?: TheiaAppFactory, ): Promise { if (args.useElectron) { - return TheiaAppLoader.launchElectron(args, factory, initialWorkspace); + return TheiaElectronAppLoader.load(args, initialWorkspace, factory); } const page = await args.browser.newPage(); return TheiaBrowserAppLoader.load(page, initialWorkspace, factory); } - - private static launchElectron( - args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs, - factory?: TheiaAppFactory, - initialWorkspace?: TheiaWorkspace - ): Promise { - const electronConfig = args.useElectron; - if (electronConfig === undefined || electronConfig.launchOptions === undefined && electronConfig.electronAppPath === undefined) { - throw Error('The Theia Playwright configuration must either specify `useElectron.electronAppPath` or `useElectron.launchOptions`'); - } - const appPath = electronConfig.electronAppPath!; - const pluginsPath = electronConfig.pluginsPath; - const launchOptions = electronConfig.launchOptions ?? new ElectronLaunchOptions(appPath, pluginsPath); - return TheiaElectronAppLoader.load(launchOptions, initialWorkspace, factory); - } } diff --git a/examples/playwright/src/theia-app.ts b/examples/playwright/src/theia-app.ts index 2d40079a88a4c..a7ebaf12bb7fa 100644 --- a/examples/playwright/src/theia-app.ts +++ b/examples/playwright/src/theia-app.ts @@ -52,6 +52,7 @@ export class TheiaApp { public constructor( public page: Page, public workspace: TheiaWorkspace, + public isElectronApp: boolean, mainPageObjects: TheiaAppMainPageObjects = new TheiaAppMainPageObjects() ) { this.statusBar = new mainPageObjects.statusBar(this); From 0063a435d4a6924ab535250610c7db36ccf15102 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 30 Oct 2023 13:19:57 +0100 Subject: [PATCH 10/19] chore: simplify initialization of TheiaApploader Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- .../playwright/src/tests/theia-app.test.ts | 18 +---------- .../src/tests/theia-explorer-view.test.ts | 24 ++------------- .../src/tests/theia-main-menu.test.ts | 24 ++------------- .../src/tests/theia-output-view.test.ts | 20 ++----------- .../src/tests/theia-preference-view.test.ts | 18 +---------- .../src/tests/theia-problems-view.test.ts | 18 +---------- .../src/tests/theia-quick-command.test.ts | 20 ++----------- .../src/tests/theia-sample-app.test.ts | 18 +---------- .../src/tests/theia-status-bar.test.ts | 18 +---------- .../src/tests/theia-terminal-view.test.ts | 18 +---------- .../src/tests/theia-text-editor.test.ts | 18 +---------- .../src/tests/theia-toolbar.test.ts | 18 +---------- .../src/tests/theia-workspace.test.ts | 30 +++++-------------- examples/playwright/src/theia-app-loader.ts | 7 +++-- examples/playwright/src/theia-app.ts | 2 +- 15 files changed, 31 insertions(+), 240 deletions(-) diff --git a/examples/playwright/src/tests/theia-app.test.ts b/examples/playwright/src/tests/theia-app.test.ts index 4316cda657e48..5b94498cef054 100644 --- a/examples/playwright/src/tests/theia-app.test.ts +++ b/examples/playwright/src/tests/theia-app.test.ts @@ -26,23 +26,7 @@ test.describe('Theia Application', () => { }); test('should load and should show main content panel', async ({ playwright, browser }) => { - let args; - if (process.env.USE_ELECTRON === 'true') { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args); + app = await TheiaAppLoader.load({ playwright, browser }); expect(await app.isMainContentPanelVisible()).toBe(true); }); diff --git a/examples/playwright/src/tests/theia-explorer-view.test.ts b/examples/playwright/src/tests/theia-explorer-view.test.ts index 490fbd808d7a0..67abffdc8c713 100644 --- a/examples/playwright/src/tests/theia-explorer-view.test.ts +++ b/examples/playwright/src/tests/theia-explorer-view.test.ts @@ -27,30 +27,12 @@ test.describe('Theia Explorer View', () => { let app: TheiaApp; let explorer: TheiaExplorerView; - let isElectron: boolean; test.beforeAll(async ({ playwright, browser }) => { - isElectron = process.env.USE_ELECTRON === 'true'; const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - let args; - if (isElectron) { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args, ws); + app = await TheiaAppLoader.load({ playwright, browser }, ws); - if (isElectron) { + if (app.isElectron) { // set trash preference to off const preferenceView = await app.openPreferences(TheiaPreferenceView); await preferenceView.setBooleanPreferenceById(PreferenceIds.Files.EnableTrash, false); @@ -164,7 +146,7 @@ test.describe('Theia Explorer View', () => { const menuItems = await menu.visibleMenuItems(); expect(menuItems).toContain('Open'); expect(menuItems).toContain('Delete'); - if (!isElectron) { + if (!app.isElectron) { expect(menuItems).toContain('Download'); } diff --git a/examples/playwright/src/tests/theia-main-menu.test.ts b/examples/playwright/src/tests/theia-main-menu.test.ts index c8d3d24d47904..e3069e68a68df 100644 --- a/examples/playwright/src/tests/theia-main-menu.test.ts +++ b/examples/playwright/src/tests/theia-main-menu.test.ts @@ -27,27 +27,9 @@ test.describe('Theia Main Menu', () => { let app: TheiaApp; let menuBar: TheiaMenuBar; - let isElectron: boolean; test.beforeAll(async ({ playwright, browser }) => { - isElectron = process.env.USE_ELECTRON === 'true'; - let args; - if (isElectron) { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args); + app = await TheiaAppLoader.load({ playwright, browser }); menuBar = app.menuBar; }); @@ -83,7 +65,7 @@ test.describe('Theia Main Menu', () => { expect(label).toBe('New Text File'); const shortCut = await menuItem?.shortCut(); - expect(shortCut).toBe(OSUtil.isMacOS ? '⌥ N' : isElectron ? 'Ctrl+N' : 'Alt+N'); + expect(shortCut).toBe(OSUtil.isMacOS ? '⌥ N' : app.isElectron ? 'Ctrl+N' : 'Alt+N'); const hasSubmenu = await menuItem?.hasSubmenu(); expect(hasSubmenu).toBe(false); @@ -121,7 +103,7 @@ test.describe('Theia Main Menu', () => { }); test('open file via file menu and cancel', async () => { - const openFileEntry = isElectron ? 'Open File...' : 'Open...'; + const openFileEntry = app.isElectron ? 'Open File...' : 'Open...'; await (await menuBar.openMenu('File')).clickMenuItem(openFileEntry); const fileDialog = await app.page.waitForSelector('div[class="dialogBlock"]'); expect(await fileDialog.isVisible()).toBe(true); diff --git a/examples/playwright/src/tests/theia-output-view.test.ts b/examples/playwright/src/tests/theia-output-view.test.ts index 65d824ef8d023..8231666eb31a0 100644 --- a/examples/playwright/src/tests/theia-output-view.test.ts +++ b/examples/playwright/src/tests/theia-output-view.test.ts @@ -22,29 +22,13 @@ import { TheiaOutputView } from '../theia-output-view'; if (process.env.USE_ELECTRON === 'true') { // TODO: remove this once the test is stable enough with electron - test.describe.skip('Theia Output View', () => {}); + test.describe.skip('Theia Output View', () => { }); } else { let app: TheiaApp; let outputView: TheiaOutputView; let testChannel: TheiaOutputViewChannel; test.describe('Theia Output View', () => { test.beforeAll(async ({ playwright, browser }) => { - let args; - if (process.env.USE_ELECTRON === 'true') { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args); + app = await TheiaAppLoader.load({ playwright, browser }); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-preference-view.test.ts b/examples/playwright/src/tests/theia-preference-view.test.ts index 83b980f58cbb3..e47431b671488 100644 --- a/examples/playwright/src/tests/theia-preference-view.test.ts +++ b/examples/playwright/src/tests/theia-preference-view.test.ts @@ -26,23 +26,7 @@ test.describe('Preference View', () => { let app: TheiaApp; test.beforeAll(async ({ playwright, browser }) => { - let args; - if (process.env.USE_ELECTRON === 'true') { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args); + app = await TheiaAppLoader.load({playwright, browser}); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-problems-view.test.ts b/examples/playwright/src/tests/theia-problems-view.test.ts index cd544504edb22..11b7a688fb23a 100644 --- a/examples/playwright/src/tests/theia-problems-view.test.ts +++ b/examples/playwright/src/tests/theia-problems-view.test.ts @@ -26,23 +26,7 @@ test.describe('Theia Problems View', () => { let app: TheiaApp; test.beforeAll(async ({ playwright, browser }) => { - let args; - if (process.env.USE_ELECTRON === 'true') { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args); + app = await TheiaAppLoader.load({ playwright, browser }); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-quick-command.test.ts b/examples/playwright/src/tests/theia-quick-command.test.ts index cd8ec06fcd67b..90cbff7e0b9b9 100644 --- a/examples/playwright/src/tests/theia-quick-command.test.ts +++ b/examples/playwright/src/tests/theia-quick-command.test.ts @@ -25,7 +25,7 @@ import { TheiaQuickCommandPalette } from '../theia-quick-command-palette'; if (process.env.USE_ELECTRON === 'true') { // TODO: remove this once the test is stable enough with electron - test.describe.skip('Theia Quick Command', () => {}); + test.describe.skip('Theia Quick Command', () => { }); } else { // the tests in this file reuse a page to run faster and thus are executed serially test.describe.configure({ mode: 'serial' }); @@ -35,23 +35,7 @@ if (process.env.USE_ELECTRON === 'true') { let quickCommand: TheiaQuickCommandPalette; test.beforeAll(async ({ playwright, browser }) => { - let args; - if (process.env.USE_ELECTRON === 'true') { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args); + app = await TheiaAppLoader.load({ playwright, browser }); quickCommand = app.quickCommandPalette; }); diff --git a/examples/playwright/src/tests/theia-sample-app.test.ts b/examples/playwright/src/tests/theia-sample-app.test.ts index 56b283b20df44..b77f5a1a0319c 100644 --- a/examples/playwright/src/tests/theia-sample-app.test.ts +++ b/examples/playwright/src/tests/theia-sample-app.test.ts @@ -43,23 +43,7 @@ test.describe('Theia Sample Application', () => { let app: TheiaSampleApp; test.beforeAll(async ({ playwright, browser }) => { - let args; - if (process.env.USE_ELECTRON === 'true') { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args, new TheiaWorkspace(), TheiaSampleApp); + app = await TheiaAppLoader.load({playwright, browser}, new TheiaWorkspace(), TheiaSampleApp); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-status-bar.test.ts b/examples/playwright/src/tests/theia-status-bar.test.ts index 603b28b193e28..f9bb801640499 100644 --- a/examples/playwright/src/tests/theia-status-bar.test.ts +++ b/examples/playwright/src/tests/theia-status-bar.test.ts @@ -30,23 +30,7 @@ test.describe('Theia Status Bar', () => { let statusBar: TheiaStatusBar; test.beforeAll(async ({ playwright, browser }) => { - let args; - if (process.env.USE_ELECTRON === 'true') { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args); + app = await TheiaAppLoader.load({ playwright, browser }); statusBar = app.statusBar; }); diff --git a/examples/playwright/src/tests/theia-terminal-view.test.ts b/examples/playwright/src/tests/theia-terminal-view.test.ts index ce6d4749a2854..be4840aaf7315 100644 --- a/examples/playwright/src/tests/theia-terminal-view.test.ts +++ b/examples/playwright/src/tests/theia-terminal-view.test.ts @@ -26,23 +26,7 @@ test.describe('Theia Terminal View', () => { test.beforeAll(async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - let args; - if (process.env.USE_ELECTRON === 'true') { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args, ws); + app = await TheiaAppLoader.load({ playwright, browser }, ws); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-text-editor.test.ts b/examples/playwright/src/tests/theia-text-editor.test.ts index 050daa2633dd4..1227e05782633 100644 --- a/examples/playwright/src/tests/theia-text-editor.test.ts +++ b/examples/playwright/src/tests/theia-text-editor.test.ts @@ -29,23 +29,7 @@ test.describe('Theia Text Editor', () => { test.beforeAll(async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - let args; - if (process.env.USE_ELECTRON === 'true') { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args, ws); + app = await TheiaAppLoader.load({ playwright, browser }, ws); // set auto-save preference to off const preferenceView = await app.openPreferences(TheiaPreferenceView); diff --git a/examples/playwright/src/tests/theia-toolbar.test.ts b/examples/playwright/src/tests/theia-toolbar.test.ts index 0bad63a9779eb..d7ea27a7c77c4 100644 --- a/examples/playwright/src/tests/theia-toolbar.test.ts +++ b/examples/playwright/src/tests/theia-toolbar.test.ts @@ -25,23 +25,7 @@ let toolbar: TheiaToolbar; test.describe('Theia Toolbar', () => { test.beforeAll(async ({ playwright, browser }) => { - let args; - if (process.env.USE_ELECTRON === 'true') { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } - app = await TheiaAppLoader.load(args); + app = await TheiaAppLoader.load({ playwright, browser }); toolbar = new TheiaToolbar(app); }); diff --git a/examples/playwright/src/tests/theia-workspace.test.ts b/examples/playwright/src/tests/theia-workspace.test.ts index 0c6924a593e24..273deeb608e82 100644 --- a/examples/playwright/src/tests/theia-workspace.test.ts +++ b/examples/playwright/src/tests/theia-workspace.test.ts @@ -14,36 +14,20 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { PlaywrightWorkerArgs, expect, test } from '@playwright/test'; -import { TheiaAppLoader, TheiaPlaywrightTestConfig } from '../theia-app-loader'; +import { expect, test } from '@playwright/test'; +import { TheiaAppLoader } from '../theia-app-loader'; import { DOT_FILES_FILTER, TheiaExplorerView } from '../theia-explorer-view'; import { TheiaWorkspace } from '../theia-workspace'; test.describe('Theia Workspace', () => { - let args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs; let isElectron: boolean; test.beforeAll(async ({ playwright, browser }) => { isElectron = process.env.USE_ELECTRON === 'true'; - if (isElectron) { - args = { - playwright: playwright, - browser: browser, - useElectron: { - electronAppPath: '../electron', - pluginsPath: '../../plugins' - } - }; - } else { - args = { - playwright: playwright, - browser: browser - }; - } }); test('should be initialized empty by default', async ({ playwright, browser }) => { if (!isElectron) { - const app = await TheiaAppLoader.load(args); + const app = await TheiaAppLoader.load({ playwright, browser }); const explorer = await app.openView(TheiaExplorerView); const fileStatElements = await explorer.visibleFileStatNodes(DOT_FILES_FILTER); expect(fileStatElements.length).toBe(0); @@ -53,7 +37,7 @@ test.describe('Theia Workspace', () => { test('should be initialized with the contents of a file location', async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - const app = await TheiaAppLoader.load(args, ws); + const app = await TheiaAppLoader.load({ playwright, browser }, ws); const explorer = await app.openView(TheiaExplorerView); // resources/sample-files1 contains two folders and one file expect(await explorer.existsDirectoryNode('sampleFolder')).toBe(true); @@ -64,7 +48,7 @@ test.describe('Theia Workspace', () => { test('should be initialized with the contents of multiple file locations', async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1', 'src/tests/resources/sample-files2']); - const app = await TheiaAppLoader.load(args, ws); + const app = await TheiaAppLoader.load({ playwright, browser }, ws); const explorer = await app.openView(TheiaExplorerView); // resources/sample-files1 contains two folders and one file expect(await explorer.existsDirectoryNode('sampleFolder')).toBe(true); @@ -77,8 +61,8 @@ test.describe('Theia Workspace', () => { test('open sample.txt via file menu', async ({ playwright, browser }) => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); - const app = await TheiaAppLoader.load(args, ws); - const menuEntry = isElectron ? 'Open File...' : 'Open...'; + const app = await TheiaAppLoader.load({ playwright, browser }, ws); + const menuEntry = app.isElectron ? 'Open File...' : 'Open...'; await (await app.menuBar.openMenu('File')).clickMenuItem(menuEntry); const fileDialog = await app.page.waitForSelector('div[class="dialogBlock"]'); diff --git a/examples/playwright/src/theia-app-loader.ts b/examples/playwright/src/theia-app-loader.ts index 73e8dd42081a8..b95c1605f094d 100644 --- a/examples/playwright/src/theia-app-loader.ts +++ b/examples/playwright/src/theia-app-loader.ts @@ -82,7 +82,10 @@ class TheiaElectronAppLoader { factory?: TheiaAppFactory, ): Promise { const workspace = initializeWorkspace(initialWorkspace); - const electronConfig = args.useElectron; + const electronConfig = args.useElectron ?? { + electronAppPath: '../electron', + pluginsPath: '../../plugins' + }; if (electronConfig === undefined || electronConfig.launchOptions === undefined && electronConfig.electronAppPath === undefined) { throw Error('The Theia Playwright configuration must either specify `useElectron.electronAppPath` or `useElectron.launchOptions`'); } @@ -165,7 +168,7 @@ export class TheiaAppLoader { initialWorkspace?: TheiaWorkspace, factory?: TheiaAppFactory, ): Promise { - if (args.useElectron) { + if (process.env.USE_ELECTRON === 'true') { return TheiaElectronAppLoader.load(args, initialWorkspace, factory); } const page = await args.browser.newPage(); diff --git a/examples/playwright/src/theia-app.ts b/examples/playwright/src/theia-app.ts index a7ebaf12bb7fa..2871f1929171b 100644 --- a/examples/playwright/src/theia-app.ts +++ b/examples/playwright/src/theia-app.ts @@ -52,7 +52,7 @@ export class TheiaApp { public constructor( public page: Page, public workspace: TheiaWorkspace, - public isElectronApp: boolean, + public isElectron: boolean, mainPageObjects: TheiaAppMainPageObjects = new TheiaAppMainPageObjects() ) { this.statusBar = new mainPageObjects.statusBar(this); From b82f413f8197fb3c4e9761d743e9d67cb5e95a1a Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 30 Oct 2023 16:00:47 +0100 Subject: [PATCH 11/19] chore: remove duplicated mode 'serial' annotations Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- examples/playwright/configs/playwright.config.ts | 1 + examples/playwright/src/tests/theia-explorer-view.test.ts | 2 -- examples/playwright/src/tests/theia-main-menu.test.ts | 2 -- examples/playwright/src/tests/theia-preference-view.test.ts | 4 +--- examples/playwright/src/tests/theia-problems-view.test.ts | 2 -- examples/playwright/src/tests/theia-quick-command.test.ts | 2 -- examples/playwright/src/tests/theia-sample-app.test.ts | 4 +--- examples/playwright/src/tests/theia-status-bar.test.ts | 2 -- examples/playwright/src/tests/theia-text-editor.test.ts | 2 -- 9 files changed, 3 insertions(+), 18 deletions(-) diff --git a/examples/playwright/configs/playwright.config.ts b/examples/playwright/configs/playwright.config.ts index ee0e56dd775c9..a74090f1ccb51 100644 --- a/examples/playwright/configs/playwright.config.ts +++ b/examples/playwright/configs/playwright.config.ts @@ -20,6 +20,7 @@ const config: PlaywrightTestConfig = { testDir: '../lib/tests', testMatch: ['**/*.js'], workers: 1, + fullyParallel: false, // Timeout for each test in milliseconds. timeout: 60 * 1000, use: { diff --git a/examples/playwright/src/tests/theia-explorer-view.test.ts b/examples/playwright/src/tests/theia-explorer-view.test.ts index 67abffdc8c713..1705244609988 100644 --- a/examples/playwright/src/tests/theia-explorer-view.test.ts +++ b/examples/playwright/src/tests/theia-explorer-view.test.ts @@ -21,8 +21,6 @@ import { PreferenceIds, TheiaPreferenceView } from '../theia-preference-view'; import { DOT_FILES_FILTER, TheiaExplorerView } from '../theia-explorer-view'; import { TheiaWorkspace } from '../theia-workspace'; -// the tests in this file reuse a page to run faster and thus are executed serially -test.describe.configure({ mode: 'serial' }); test.describe('Theia Explorer View', () => { let app: TheiaApp; diff --git a/examples/playwright/src/tests/theia-main-menu.test.ts b/examples/playwright/src/tests/theia-main-menu.test.ts index e3069e68a68df..f00fcb6727270 100644 --- a/examples/playwright/src/tests/theia-main-menu.test.ts +++ b/examples/playwright/src/tests/theia-main-menu.test.ts @@ -21,8 +21,6 @@ import { TheiaAboutDialog } from '../theia-about-dialog'; import { TheiaMenuBar } from '../theia-main-menu'; import { OSUtil } from '../util'; -// the tests in this file reuse a page to run faster and thus are executed serially -test.describe.configure({ mode: 'serial' }); test.describe('Theia Main Menu', () => { let app: TheiaApp; diff --git a/examples/playwright/src/tests/theia-preference-view.test.ts b/examples/playwright/src/tests/theia-preference-view.test.ts index e47431b671488..bdaf34266ebe6 100644 --- a/examples/playwright/src/tests/theia-preference-view.test.ts +++ b/examples/playwright/src/tests/theia-preference-view.test.ts @@ -19,14 +19,12 @@ import { TheiaApp } from '../theia-app'; import { TheiaAppLoader } from '../theia-app-loader'; import { DefaultPreferences, PreferenceIds, TheiaPreferenceView } from '../theia-preference-view'; -// the tests in this file reuse a page to run faster and thus are executed serially -test.describe.configure({ mode: 'serial' }); test.describe('Preference View', () => { let app: TheiaApp; test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({playwright, browser}); + app = await TheiaAppLoader.load({ playwright, browser }); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-problems-view.test.ts b/examples/playwright/src/tests/theia-problems-view.test.ts index 11b7a688fb23a..611080dc6516b 100644 --- a/examples/playwright/src/tests/theia-problems-view.test.ts +++ b/examples/playwright/src/tests/theia-problems-view.test.ts @@ -19,8 +19,6 @@ import { TheiaApp } from '../theia-app'; import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaProblemsView } from '../theia-problem-view'; -// the tests in this file reuse a page to run faster and thus are executed serially -test.describe.configure({ mode: 'serial' }); test.describe('Theia Problems View', () => { let app: TheiaApp; diff --git a/examples/playwright/src/tests/theia-quick-command.test.ts b/examples/playwright/src/tests/theia-quick-command.test.ts index 90cbff7e0b9b9..e4b5354e0b15d 100644 --- a/examples/playwright/src/tests/theia-quick-command.test.ts +++ b/examples/playwright/src/tests/theia-quick-command.test.ts @@ -27,8 +27,6 @@ if (process.env.USE_ELECTRON === 'true') { // TODO: remove this once the test is stable enough with electron test.describe.skip('Theia Quick Command', () => { }); } else { - // the tests in this file reuse a page to run faster and thus are executed serially - test.describe.configure({ mode: 'serial' }); test.describe('Theia Quick Command', () => { let app: TheiaApp; diff --git a/examples/playwright/src/tests/theia-sample-app.test.ts b/examples/playwright/src/tests/theia-sample-app.test.ts index b77f5a1a0319c..48dd024d3abfb 100644 --- a/examples/playwright/src/tests/theia-sample-app.test.ts +++ b/examples/playwright/src/tests/theia-sample-app.test.ts @@ -36,14 +36,12 @@ class TheiaSampleApp extends TheiaApp { } } -// the tests in this file reuse a page to run faster and thus are executed serially -test.describe.configure({ mode: 'serial' }); test.describe('Theia Sample Application', () => { let app: TheiaSampleApp; test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({playwright, browser}, new TheiaWorkspace(), TheiaSampleApp); + app = await TheiaAppLoader.load({ playwright, browser }, new TheiaWorkspace(), TheiaSampleApp); }); test.afterAll(async () => { diff --git a/examples/playwright/src/tests/theia-status-bar.test.ts b/examples/playwright/src/tests/theia-status-bar.test.ts index f9bb801640499..871e7c468519e 100644 --- a/examples/playwright/src/tests/theia-status-bar.test.ts +++ b/examples/playwright/src/tests/theia-status-bar.test.ts @@ -22,8 +22,6 @@ import { TheiaProblemIndicator } from '../theia-problem-indicator'; import { TheiaStatusBar } from '../theia-status-bar'; import { TheiaToggleBottomIndicator } from '../theia-toggle-bottom-indicator'; -// the tests in this file reuse a page to run faster and thus are executed serially -test.describe.configure({ mode: 'serial' }); test.describe('Theia Status Bar', () => { let app: TheiaApp; diff --git a/examples/playwright/src/tests/theia-text-editor.test.ts b/examples/playwright/src/tests/theia-text-editor.test.ts index 1227e05782633..395357d99e6da 100644 --- a/examples/playwright/src/tests/theia-text-editor.test.ts +++ b/examples/playwright/src/tests/theia-text-editor.test.ts @@ -21,8 +21,6 @@ import { DefaultPreferences, PreferenceIds, TheiaPreferenceView } from '../theia import { TheiaTextEditor } from '../theia-text-editor'; import { TheiaWorkspace } from '../theia-workspace'; -// the tests in this file reuse a page to run faster and thus are executed serially -test.describe.configure({ mode: 'serial' }); test.describe('Theia Text Editor', () => { let app: TheiaApp; From 8a43294edf38c1b5dc3b0fe4aabc0548749b815f Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 30 Oct 2023 16:33:07 +0100 Subject: [PATCH 12/19] chore: remove unnecessary open() calls in test Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- examples/playwright/src/tests/theia-quick-command.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/playwright/src/tests/theia-quick-command.test.ts b/examples/playwright/src/tests/theia-quick-command.test.ts index e4b5354e0b15d..d55dc9d73b601 100644 --- a/examples/playwright/src/tests/theia-quick-command.test.ts +++ b/examples/playwright/src/tests/theia-quick-command.test.ts @@ -51,7 +51,6 @@ if (process.env.USE_ELECTRON === 'true') { }); test('should trigger \'About\' command after typing', async () => { - await quickCommand.open(); await quickCommand.type('About'); await quickCommand.trigger('About'); expect(await quickCommand.isOpen()).toBe(false); @@ -66,7 +65,6 @@ if (process.env.USE_ELECTRON === 'true') { }); test('should trigger \'Toggle Explorer View\' command after typing', async () => { - await quickCommand.open(); await quickCommand.type('Toggle Explorer'); await quickCommand.trigger('Toggle Explorer View'); expect(await quickCommand.isOpen()).toBe(false); From 0d19547866ae851681ead3e631f52ebc2efa7fea Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 30 Oct 2023 17:13:32 +0100 Subject: [PATCH 13/19] chore: remove class `ElectronLaunchOptions` As suggested in the code review, we remove the explicit class and convert the options within `TheiaElectronAppLoader` if necessary. Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- examples/playwright/src/theia-app-loader.ts | 72 +++++++++------------ 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/examples/playwright/src/theia-app-loader.ts b/examples/playwright/src/theia-app-loader.ts index b95c1605f094d..9b3eea86af92c 100644 --- a/examples/playwright/src/theia-app-loader.ts +++ b/examples/playwright/src/theia-app-loader.ts @@ -71,7 +71,6 @@ class TheiaBrowserAppLoader { } } } - } class TheiaElectronAppLoader { @@ -91,8 +90,13 @@ class TheiaElectronAppLoader { } const appPath = electronConfig.electronAppPath!; const pluginsPath = electronConfig.pluginsPath; - const launchOptions = electronConfig.launchOptions ?? new ElectronLaunchOptions(appPath, pluginsPath); + const launchOptions = electronConfig.launchOptions ?? { + additionalArgs: ['--no-cluster'], + electronAppPath: appPath, + pluginsPath: pluginsPath + }; const playwrightOptions = this.toPlaywrightOptions(launchOptions, workspace); + console.log(`Launching Electron with options: ${JSON.stringify(playwrightOptions)}`); const electronApp = await electron.launch(playwrightOptions); const page = await electronApp.firstWindow(); const appFactory = theiaAppFactory(factory); @@ -102,49 +106,33 @@ class TheiaElectronAppLoader { } protected static toPlaywrightOptions( - electronLaunchOptions: ElectronLaunchOptions | object, + electronLaunchOptions: { additionalArgs: string[], electronAppPath: string, pluginsPath?: string } | object, workspace?: TheiaWorkspace - ): object { - if (electronLaunchOptions instanceof ElectronLaunchOptions) { - return electronLaunchOptions.playwrightOptions(workspace); - } - return electronLaunchOptions; - } -} - -export class ElectronLaunchOptions { - - constructor( - protected readonly electronAppPath: string, - protected readonly pluginsPath?: string, - protected readonly additionalArgs: string[] = ['--no-cluster'] - ) { } - - playwrightOptions(workspace?: TheiaWorkspace): object { - 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); - } + ): { executablePath: string, args: string[] } | object { + if ('additionalArgs' in electronLaunchOptions && 'electronAppPath' in electronLaunchOptions) { + const executablePath = path.normalize(path.join(electronLaunchOptions.electronAppPath, 'node_modules/.bin/electron')) + (OSUtil.isWindows ? '.cmd' : ''); + if (!fs.existsSync(executablePath)) { + const errorMsg = `executablePath: ${executablePath} does not exist`; + console.log(errorMsg); + throw new Error(errorMsg); + } + const args = [ + electronLaunchOptions.electronAppPath, + ...electronLaunchOptions.additionalArgs, + `--app-project-path=${electronLaunchOptions.electronAppPath}` + ]; + if (electronLaunchOptions.pluginsPath) { + args.push(`--plugins=local-dir:${electronLaunchOptions.pluginsPath}`); + } + if (workspace) { + args.push(workspace.path); + } - const args: string[] = []; - args.push(this.electronAppPath); - args.push(...this.additionalArgs); - args.push(`--app-project-path=${this.electronAppPath}`); - if (this.pluginsPath) { - args.push(`--plugins=local-dir:${this.pluginsPath}`); - }; - if (workspace) { - args.push(workspace.path); + process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS = '1'; + process.env.THEIA_ELECTRON_NO_EARLY_WINDOW = '1'; + return { executablePath: executablePath, args: args }; } - process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS = '1'; - process.env.THEIA_ELECTRON_NO_EARLY_WINDOW = '1'; - console.log(`Launching Electron: ${executablePath} ${args.join(' ')}`); - return { executablePath, args }; + return electronLaunchOptions; } } From c09bdce0e7031c9c7c4616ddd16ebbf646fd50d5 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Tue, 31 Oct 2023 12:39:28 +0100 Subject: [PATCH 14/19] fix: always set env variables for playwright-electron Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- examples/playwright/src/theia-app-loader.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/playwright/src/theia-app-loader.ts b/examples/playwright/src/theia-app-loader.ts index 9b3eea86af92c..b600eac978f6d 100644 --- a/examples/playwright/src/theia-app-loader.ts +++ b/examples/playwright/src/theia-app-loader.ts @@ -128,8 +128,6 @@ class TheiaElectronAppLoader { args.push(workspace.path); } - process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS = '1'; - process.env.THEIA_ELECTRON_NO_EARLY_WINDOW = '1'; return { executablePath: executablePath, args: args }; } return electronLaunchOptions; @@ -157,6 +155,9 @@ export class TheiaAppLoader { factory?: TheiaAppFactory, ): Promise { if (process.env.USE_ELECTRON === 'true') { + // disable native elements and early window to avoid issues with the electron app + process.env.THEIA_ELECTRON_DISABLE_NATIVE_ELEMENTS = '1'; + process.env.THEIA_ELECTRON_NO_EARLY_WINDOW = '1'; return TheiaElectronAppLoader.load(args, initialWorkspace, factory); } const page = await args.browser.newPage(); From c3e927269fadaa63eef3db13e3e85cfefa5b4cee Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 13 Nov 2023 12:25:53 +0100 Subject: [PATCH 15/19] chore: use namespaces instead of static classes in theia-app-loader Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- examples/playwright/src/theia-app-loader.ts | 51 +++++++++++---------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/examples/playwright/src/theia-app-loader.ts b/examples/playwright/src/theia-app-loader.ts index b600eac978f6d..060453064dd81 100644 --- a/examples/playwright/src/theia-app-loader.ts +++ b/examples/playwright/src/theia-app-loader.ts @@ -25,6 +25,19 @@ export interface TheiaAppFactory { new(page: Page, initialWorkspace?: TheiaWorkspace, isElectron?: boolean, mainPageObjects?: TheiaAppMainPageObjects): T; } +// TODO this is just a sketch, we need a proper way to configure tests and pass this configuration to the `TheiaAppLoader`: +export interface TheiaPlaywrightTestConfig { + useElectron?: { + /** Path to the Theia Electron app package (absolute or relative to this package). */ + electronAppPath?: string, + /** Path to the folder containing the plugins to load (absolute or relative to this package). */ + pluginsPath?: string, + // eslint-disable-next-line max-len + /** Electron launch options as [specified by Playwright](https://github.com/microsoft/playwright/blob/396487fc4c19bf27554eac9beea9db135e96cfb4/packages/playwright-core/types/types.d.ts#L14182). */ + launchOptions?: object, + } +} + function theiaAppFactory(factory?: TheiaAppFactory): TheiaAppFactory { return (factory ?? TheiaApp) as TheiaAppFactory; } @@ -35,30 +48,30 @@ function initializeWorkspace(initialWorkspace?: TheiaWorkspace): TheiaWorkspace return workspace; } -class TheiaBrowserAppLoader { +namespace TheiaBrowserAppLoader { - static async load( + export async function load( page: Page, initialWorkspace?: TheiaWorkspace, factory?: TheiaAppFactory ): Promise { const workspace = initializeWorkspace(initialWorkspace); - return this.createAndLoad(page, workspace, factory); + return createAndLoad(page, workspace, factory); } - protected static async createAndLoad( + async function createAndLoad( page: Page, workspace: TheiaWorkspace, factory?: TheiaAppFactory ): Promise { const appFactory = theiaAppFactory(factory); const app = new appFactory(page, workspace, false); - await this.loadOrReload(app, '/#' + app.workspace.urlEncodedPath); + await loadOrReload(app, '/#' + app.workspace.urlEncodedPath); await app.waitForShellAndInitialized(); return app; } - protected static async loadOrReload(app: TheiaApp, url: string): Promise { + async function loadOrReload(app: TheiaApp, url: string): Promise { if (app.page.url() === url) { await app.page.reload(); } else { @@ -73,9 +86,9 @@ class TheiaBrowserAppLoader { } } -class TheiaElectronAppLoader { +namespace TheiaElectronAppLoader { - static async load( + export async function load( args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs, initialWorkspace?: TheiaWorkspace, factory?: TheiaAppFactory, @@ -95,17 +108,18 @@ class TheiaElectronAppLoader { electronAppPath: appPath, pluginsPath: pluginsPath }; - const playwrightOptions = this.toPlaywrightOptions(launchOptions, workspace); + const playwrightOptions = toPlaywrightOptions(launchOptions, workspace); console.log(`Launching Electron with options: ${JSON.stringify(playwrightOptions)}`); const electronApp = await electron.launch(playwrightOptions); const page = await electronApp.firstWindow(); + const appFactory = theiaAppFactory(factory); const app = new appFactory(page, workspace, true); await app.waitForShellAndInitialized(); return app; } - protected static toPlaywrightOptions( + export function toPlaywrightOptions( electronLaunchOptions: { additionalArgs: string[], electronAppPath: string, pluginsPath?: string } | object, workspace?: TheiaWorkspace ): { executablePath: string, args: string[] } | object { @@ -134,22 +148,9 @@ class TheiaElectronAppLoader { } } -// TODO this is just a sketch, we need a proper way to configure tests and pass this configuration to the `TheiaAppLoader`: - -export interface TheiaPlaywrightTestConfig { - useElectron?: { - /** Path to the Theia Electron app package (absolute or relative to this package). */ - electronAppPath?: string, - /** Path to the folder containing the plugins to load (absolute or relative to this package). */ - pluginsPath?: string, - // eslint-disable-next-line max-len - /** Electron launch options as [specified by Playwright](https://github.com/microsoft/playwright/blob/396487fc4c19bf27554eac9beea9db135e96cfb4/packages/playwright-core/types/types.d.ts#L14182). */ - launchOptions?: object, - } -} +export namespace TheiaAppLoader { -export class TheiaAppLoader { - static async load( + export async function load( args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs, initialWorkspace?: TheiaWorkspace, factory?: TheiaAppFactory, From e5aebaaae63b522e74fd0417f905b6812a0482e7 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Mon, 13 Nov 2023 12:57:24 +0100 Subject: [PATCH 16/19] chore: remove TheiaAppMainPageObjects class Replaced with factory methods within TheiaApp class. Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- examples/playwright/src/theia-app-loader.ts | 4 ++-- examples/playwright/src/theia-app.ts | 25 ++++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/examples/playwright/src/theia-app-loader.ts b/examples/playwright/src/theia-app-loader.ts index 060453064dd81..08b9084c5d3ba 100644 --- a/examples/playwright/src/theia-app-loader.ts +++ b/examples/playwright/src/theia-app-loader.ts @@ -17,12 +17,12 @@ 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 { TheiaApp } from './theia-app'; import { TheiaWorkspace } from './theia-workspace'; import { OSUtil } from './util'; export interface TheiaAppFactory { - new(page: Page, initialWorkspace?: TheiaWorkspace, isElectron?: boolean, mainPageObjects?: TheiaAppMainPageObjects): T; + new(page: Page, initialWorkspace?: TheiaWorkspace, isElectron?: boolean): T; } // TODO this is just a sketch, we need a proper way to configure tests and pass this configuration to the `TheiaAppLoader`: diff --git a/examples/playwright/src/theia-app.ts b/examples/playwright/src/theia-app.ts index 2871f1929171b..ab7aeba5e71e9 100644 --- a/examples/playwright/src/theia-app.ts +++ b/examples/playwright/src/theia-app.ts @@ -35,12 +35,6 @@ export const DefaultTheiaAppData: TheiaAppData = { shellSelector: '.theia-ApplicationShell' }; -export class TheiaAppMainPageObjects { - statusBar: new (page: TheiaApp) => TheiaStatusBar = TheiaStatusBar; - quickCommandPalette: new (page: TheiaApp) => TheiaQuickCommandPalette = TheiaQuickCommandPalette; - menuBar: new (page: TheiaApp) => TheiaMenuBar = TheiaMenuBar; -} - export class TheiaApp { statusBar: TheiaStatusBar; @@ -53,11 +47,22 @@ export class TheiaApp { public page: Page, public workspace: TheiaWorkspace, public isElectron: boolean, - mainPageObjects: TheiaAppMainPageObjects = new TheiaAppMainPageObjects() ) { - this.statusBar = new mainPageObjects.statusBar(this); - this.quickCommandPalette = new mainPageObjects.quickCommandPalette(this); - this.menuBar = new mainPageObjects.menuBar(this); + this.statusBar = this.createStatusBar(); + this.quickCommandPalette = this.createQuickCommandPalette(); + this.menuBar = this.createMenuBar(); + } + + protected createStatusBar(): TheiaStatusBar { + return new TheiaStatusBar(this); + } + + protected createQuickCommandPalette(): TheiaQuickCommandPalette { + return new TheiaQuickCommandPalette(this); + } + + protected createMenuBar(): TheiaMenuBar { + return new TheiaMenuBar(this); } async isShellVisible(): Promise { From fad7d2347ff0bced4debdca16acc4f3b23786846 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Tue, 14 Nov 2023 11:54:53 +0100 Subject: [PATCH 17/19] fix: revert path transformation for windows/macos path.normalize() does not transform drive letters to lower case, so we revert back to the original version. Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- examples/playwright/src/theia-workspace.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/examples/playwright/src/theia-workspace.ts b/examples/playwright/src/theia-workspace.ts index ab1f9c391ef17..f7385bca5f84f 100644 --- a/examples/playwright/src/theia-workspace.ts +++ b/examples/playwright/src/theia-workspace.ts @@ -15,7 +15,6 @@ // ***************************************************************************** import * as fs from 'fs-extra'; -import * as path from 'path'; import { resolve } from 'path'; import { OSUtil, urlEncodePath } from './util'; @@ -30,7 +29,7 @@ export class TheiaWorkspace { * @param {string[]} pathOfFilesToInitialize Path to files or folders that shall be copied to the workspace */ constructor(protected pathOfFilesToInitialize?: string[]) { - this.workspacePath = fs.mkdtempSync(path.join(OSUtil.tmpDir, 'cloud-ws-')); + this.workspacePath = fs.mkdtempSync(`${OSUtil.tmpDir}${OSUtil.fileSeparator}cloud-ws-`); } /** Performs the file system operations preparing the workspace location synchronously. */ @@ -47,7 +46,15 @@ export class TheiaWorkspace { } get path(): string { - return path.normalize(this.workspacePath); + let workspacePath = this.workspacePath; + if (!OSUtil.osStartsWithFileSeparator(this.workspacePath)) { + workspacePath = `${OSUtil.fileSeparator}${workspacePath}`; + } + if (OSUtil.isWindows) { + // Drive letters in windows paths have to be lower case + workspacePath = workspacePath.replace(/.:/, matchedChar => matchedChar.toLowerCase()); + } + return workspacePath; } get urlEncodedPath(): string { From 2f78ce15856b42509fe29a7a7b372c3434ce9276 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Fri, 17 Nov 2023 16:25:53 +0100 Subject: [PATCH 18/19] fix: re-enable all playwright tests in the test suite Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- .../src/tests/theia-output-view.test.ts | 120 +++++++++--------- .../src/tests/theia-quick-command.test.ts | 97 +++++++------- 2 files changed, 103 insertions(+), 114 deletions(-) diff --git a/examples/playwright/src/tests/theia-output-view.test.ts b/examples/playwright/src/tests/theia-output-view.test.ts index 8231666eb31a0..ff741224124e4 100644 --- a/examples/playwright/src/tests/theia-output-view.test.ts +++ b/examples/playwright/src/tests/theia-output-view.test.ts @@ -20,72 +20,66 @@ import { TheiaApp } from '../theia-app'; import { TheiaAppLoader } from '../theia-app-loader'; import { TheiaOutputView } from '../theia-output-view'; -if (process.env.USE_ELECTRON === 'true') { - // TODO: remove this once the test is stable enough with electron - test.describe.skip('Theia Output View', () => { }); -} else { - let app: TheiaApp; let outputView: TheiaOutputView; let testChannel: TheiaOutputViewChannel; - test.describe('Theia Output View', () => { +let app: TheiaApp; let outputView: TheiaOutputView; let testChannel: TheiaOutputViewChannel; +test.describe('Theia Output View', () => { - test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({ playwright, browser }); - }); - - test.afterAll(async () => { - await app.page.close(); - }); + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); + }); - test('should open the output view and check if is visible and active', async () => { - outputView = await app.openView(TheiaOutputView); - expect(await outputView.isTabVisible()).toBe(true); - expect(await outputView.isDisplayed()).toBe(true); - expect(await outputView.isActive()).toBe(true); - }); - test('should be opened at the bottom and have the title "Output"', async () => { - expect(await outputView.isInSidePanel()).toBe(false); - expect(await outputView.side()).toBe('bottom'); - expect(await outputView.title()).toBe('Output'); - }); - test('should be closable', async () => { - expect(await outputView.isClosable()).toBe(true); - await outputView.close(); - expect(await outputView.isTabVisible()).toBe(false); - expect(await outputView.isDisplayed()).toBe(false); - expect(await outputView.isActive()).toBe(false); - }); - test('should select a test output channel', async () => { - outputView = await app.openView(TheiaOutputView); - expect(await outputView.isTabVisible()).toBe(true); - expect(await outputView.isDisplayed()).toBe(true); - expect(await outputView.isActive()).toBe(true); + test.afterAll(async () => { + await app.page.close(); + }); - const testChannelName = 'API Sample: my test channel'; - expect(await outputView.selectOutputChannel(testChannelName)).toBe(true); - }); - test('should check if the output view of the test output channel', async () => { - const testChannelName = 'API Sample: my test channel'; - expect(await outputView.isOutputChannelSelected(testChannelName)); - const channel = await outputView.getOutputChannel(testChannelName); - expect(channel).toBeDefined; - testChannel = channel!; - expect(await testChannel!.isDisplayed()).toBe(true); - }); - test('should check if the output view test channel shows the test output', async () => { - expect(await testChannel.numberOfLines()).toBe(5); - expect(await testChannel.textContentOfLineByLineNumber(1)).toMatch('hello info1'); - expect(await testChannel.maxSeverityOfLineByLineNumber(1)).toMatch('info'); - expect(await testChannel.textContentOfLineByLineNumber(2)).toMatch('hello info2'); - expect(await testChannel.maxSeverityOfLineByLineNumber(2)).toMatch('info'); - expect(await testChannel.textContentOfLineByLineNumber(3)).toMatch('hello error'); - expect(await testChannel.maxSeverityOfLineByLineNumber(3)).toMatch('error'); - expect(await testChannel.textContentOfLineByLineNumber(4)).toMatch('hello warning'); - expect(await testChannel.maxSeverityOfLineByLineNumber(4)).toMatch('warning'); - expect(await testChannel.textContentOfLineByLineNumber(5)).toMatch( - 'inlineInfo1 inlineWarning inlineError inlineInfo2' - ); - expect(await testChannel.maxSeverityOfLineByLineNumber(5)).toMatch('error'); - }); + test('should open the output view and check if is visible and active', async () => { + outputView = await app.openView(TheiaOutputView); + expect(await outputView.isTabVisible()).toBe(true); + expect(await outputView.isDisplayed()).toBe(true); + expect(await outputView.isActive()).toBe(true); + }); + test('should be opened at the bottom and have the title "Output"', async () => { + expect(await outputView.isInSidePanel()).toBe(false); + expect(await outputView.side()).toBe('bottom'); + expect(await outputView.title()).toBe('Output'); + }); + test('should be closable', async () => { + expect(await outputView.isClosable()).toBe(true); + await outputView.close(); + expect(await outputView.isTabVisible()).toBe(false); + expect(await outputView.isDisplayed()).toBe(false); + expect(await outputView.isActive()).toBe(false); + }); + test('should select a test output channel', async () => { + outputView = await app.openView(TheiaOutputView); + expect(await outputView.isTabVisible()).toBe(true); + expect(await outputView.isDisplayed()).toBe(true); + expect(await outputView.isActive()).toBe(true); + const testChannelName = 'API Sample: my test channel'; + expect(await outputView.selectOutputChannel(testChannelName)).toBe(true); + }); + test('should check if the output view of the test output channel', async () => { + const testChannelName = 'API Sample: my test channel'; + expect(await outputView.isOutputChannelSelected(testChannelName)); + const channel = await outputView.getOutputChannel(testChannelName); + expect(channel).toBeDefined; + testChannel = channel!; + expect(await testChannel!.isDisplayed()).toBe(true); + }); + test('should check if the output view test channel shows the test output', async () => { + expect(await testChannel.numberOfLines()).toBe(5); + expect(await testChannel.textContentOfLineByLineNumber(1)).toMatch('hello info1'); + expect(await testChannel.maxSeverityOfLineByLineNumber(1)).toMatch('info'); + expect(await testChannel.textContentOfLineByLineNumber(2)).toMatch('hello info2'); + expect(await testChannel.maxSeverityOfLineByLineNumber(2)).toMatch('info'); + expect(await testChannel.textContentOfLineByLineNumber(3)).toMatch('hello error'); + expect(await testChannel.maxSeverityOfLineByLineNumber(3)).toMatch('error'); + expect(await testChannel.textContentOfLineByLineNumber(4)).toMatch('hello warning'); + expect(await testChannel.maxSeverityOfLineByLineNumber(4)).toMatch('warning'); + expect(await testChannel.textContentOfLineByLineNumber(5)).toMatch( + 'inlineInfo1 inlineWarning inlineError inlineInfo2' + ); + expect(await testChannel.maxSeverityOfLineByLineNumber(5)).toMatch('error'); }); -} +}); diff --git a/examples/playwright/src/tests/theia-quick-command.test.ts b/examples/playwright/src/tests/theia-quick-command.test.ts index d55dc9d73b601..4f9b8352cc4b6 100644 --- a/examples/playwright/src/tests/theia-quick-command.test.ts +++ b/examples/playwright/src/tests/theia-quick-command.test.ts @@ -23,63 +23,58 @@ import { TheiaNotificationIndicator } from '../theia-notification-indicator'; import { TheiaNotificationOverlay } from '../theia-notification-overlay'; import { TheiaQuickCommandPalette } from '../theia-quick-command-palette'; -if (process.env.USE_ELECTRON === 'true') { - // TODO: remove this once the test is stable enough with electron - test.describe.skip('Theia Quick Command', () => { }); -} else { - test.describe('Theia Quick Command', () => { +test.describe('Theia Quick Command', () => { - let app: TheiaApp; - let quickCommand: TheiaQuickCommandPalette; + let app: TheiaApp; + let quickCommand: TheiaQuickCommandPalette; - test.beforeAll(async ({ playwright, browser }) => { - app = await TheiaAppLoader.load({ playwright, browser }); - quickCommand = app.quickCommandPalette; - }); - - test.afterAll(async () => { - await app.page.close(); - }); + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); + quickCommand = app.quickCommandPalette; + }); - test('should show quick command palette', async () => { - await quickCommand.open(); - expect(await quickCommand.isOpen()).toBe(true); - await quickCommand.hide(); - expect(await quickCommand.isOpen()).toBe(false); - await quickCommand.open(); - expect(await quickCommand.isOpen()).toBe(true); - }); + test.afterAll(async () => { + await app.page.close(); + }); - test('should trigger \'About\' command after typing', async () => { - 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(); - expect(await aboutDialog.isVisible()).toBe(false); + test('should show quick command palette', async () => { + await quickCommand.open(); + expect(await quickCommand.isOpen()).toBe(true); + await quickCommand.hide(); + expect(await quickCommand.isOpen()).toBe(false); + await quickCommand.open(); + expect(await quickCommand.isOpen()).toBe(true); + }); - await quickCommand.type('Select All'); - await quickCommand.trigger('Select All'); - expect(await quickCommand.isOpen()).toBe(false); - }); + test('should trigger \'About\' command after typing', async () => { + 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(); + expect(await aboutDialog.isVisible()).toBe(false); - test('should trigger \'Toggle Explorer View\' command after typing', async () => { - 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.type('Select All'); + await quickCommand.trigger('Select All'); + expect(await quickCommand.isOpen()).toBe(false); + }); - test('should trigger \'Quick Input: Test Positive Integer\' command by confirming via Enter', async () => { - await quickCommand.type('Test Positive', true); - expect(await quickCommand.isOpen()).toBe(true); - await quickCommand.type('6', true); - const notificationIndicator = new TheiaNotificationIndicator(app); - const notification = new TheiaNotificationOverlay(app, notificationIndicator); - expect(await notification.isEntryVisible('Positive Integer: 6')).toBe(true); - }); + test('should trigger \'Toggle Explorer View\' command after typing', async () => { + 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); + }); + test('should trigger \'Quick Input: Test Positive Integer\' command by confirming via Enter', async () => { + await quickCommand.type('Test Positive', true); + expect(await quickCommand.isOpen()).toBe(true); + await quickCommand.type('6', true); + const notificationIndicator = new TheiaNotificationIndicator(app); + const notification = new TheiaNotificationOverlay(app, notificationIndicator); + expect(await notification.isEntryVisible('Positive Integer: 6')).toBe(true); }); -} + +}); From f8f01fe3307cdd1100b8e7b2bac591a1464d2cc4 Mon Sep 17 00:00:00 2001 From: Olaf Lessenich Date: Fri, 17 Nov 2023 16:27:36 +0100 Subject: [PATCH 19/19] fix: disable playwright-electron workflow Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich --- .github/workflows/playwright.yml | 11 +---------- examples/playwright/package.json | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f91bd3926824d..953096017ee3d 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -43,15 +43,6 @@ jobs: NODE_OPTIONS: --max_old_space_size=4096 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # https://github.com/microsoft/vscode-ripgrep/issues/9 - - name: Build Electron - shell: bash - run: | - yarn --skip-integrity-check --network-timeout 100000 - yarn electron build - env: - NODE_OPTIONS: --max_old_space_size=4096 - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # https://github.com/microsoft/vscode-ripgrep/issues/9 - - name: Build Playwright shell: bash run: | @@ -60,4 +51,4 @@ jobs: - name: Test (playwright) uses: GabrielBB/xvfb-action@v1 with: - run: yarn --cwd examples/playwright ui-tests-ci + run: yarn --cwd examples/playwright ui-tests-ci diff --git a/examples/playwright/package.json b/examples/playwright/package.json index 61a3ca6c57b16..ff0a61c5f2370 100644 --- a/examples/playwright/package.json +++ b/examples/playwright/package.json @@ -21,7 +21,7 @@ "playwright:install": "playwright install chromium", "ui-tests": "yarn build && playwright test --config=./configs/playwright.config.ts", "ui-tests-electron": "yarn build && USE_ELECTRON=true playwright test --config=./configs/playwright.config.ts", - "ui-tests-ci": "yarn build && playwright test --config=./configs/playwright.ci.config.ts && USE_ELECTRON=true playwright test --config=./configs/playwright.ci.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"