diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 0473f62bb2726..953096017ee3d 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -51,4 +51,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 new file mode 100644 index 0000000000000..4fa758b32a3c9 --- /dev/null +++ b/examples/playwright/configs/playwright.ci.config.ts @@ -0,0 +1,27 @@ +// ***************************************************************************** +// 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: 2, + reporter: [['list'], ['allure-playwright'], ['github']] +}; + +export default ciConfig; diff --git a/examples/playwright/playwright.config.ts b/examples/playwright/configs/playwright.config.ts similarity index 55% rename from examples/playwright/playwright.config.ts rename to examples/playwright/configs/playwright.config.ts index f07dd808d837f..a74090f1ccb51 100644 --- a/examples/playwright/playwright.config.ts +++ b/examples/playwright/configs/playwright.config.ts @@ -1,5 +1,5 @@ // ***************************************************************************** -// Copyright (C) 2021-2023 logi.cals GmbH, EclipseSource and others. +// 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 @@ -11,41 +11,35 @@ // 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 +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** -import { defineConfig } from '@playwright/test'; +import { PlaywrightTestConfig } 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, +const config: PlaywrightTestConfig = { + testDir: '../lib/tests', + testMatch: ['**/*.js'], + workers: 1, + fullyParallel: false, // Timeout for each test in milliseconds. - timeout: 30 * 1000, + timeout: 60 * 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 } + screenshot: 'only-on-failure' }, preserveOutput: 'failures-only', - reporter: process.env.CI - ? [['list'], ['allure-playwright'], ['github']] - : [['list'], ['allure-playwright']], + 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/src/tests/fixtures/theia-fixture.ts b/examples/playwright/configs/playwright.debug.config.ts similarity index 71% rename from examples/playwright/src/tests/fixtures/theia-fixture.ts rename to examples/playwright/configs/playwright.debug.config.ts index 3c1d27efde1c7..41ac56377b9ab 100644 --- a/examples/playwright/src/tests/fixtures/theia-fixture.ts +++ b/examples/playwright/configs/playwright.debug.config.ts @@ -11,15 +11,17 @@ // 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 +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** -import test, { Page } from '@playwright/test'; +import { PlaywrightTestConfig } from '@playwright/test'; -export let page: Page; +import baseConfig from './playwright.config'; -test.beforeAll(async ({ browser }) => { - page = await browser.newPage(); -}); +const debugConfig: PlaywrightTestConfig = { + ...baseConfig, + workers: 1, + timeout: 15000000 +}; -export default test; +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..ff0a61c5f2370 100644 --- a/examples/playwright/package.json +++ b/examples/playwright/package.json @@ -19,8 +19,10 @@ "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-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", + "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/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/theia-app.test.ts b/examples/playwright/src/tests/theia-app.test.ts index 157e279899d83..5b94498cef054 100644 --- a/examples/playwright/src/tests/theia-app.test.ts +++ b/examples/playwright/src/tests/theia-app.test.ts @@ -14,20 +14,19 @@ // 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 { TheiaApp } from '../theia-app'; -import { expect } from '@playwright/test'; -import test, { page } from './fixtures/theia-fixture'; - -let app: TheiaApp; - test.describe('Theia Application', () => { + let app: TheiaApp; - test('should load', async () => { - app = await TheiaApp.load(page); + test.afterAll(async () => { + await app.page.close(); }); - test('should show main content panel', async () => { + test('should load and should show main content panel', async ({ playwright, browser }) => { + 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 6ea87938d7acb..1705244609988 100644 --- a/examples/playwright/src/tests/theia-explorer-view.test.ts +++ b/examples/playwright/src/tests/theia-explorer-view.test.ts @@ -14,24 +14,37 @@ // 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 { 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'; -import test, { page } from './fixtures/theia-fixture'; - -let app: TheiaApp; -let explorer: TheiaExplorerView; test.describe('Theia Explorer View', () => { - test.beforeAll(async () => { + let app: TheiaApp; + let explorer: TheiaExplorerView; + + 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); + + if (app.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(); }); + 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); @@ -131,7 +144,9 @@ test.describe('Theia Explorer View', () => { const menuItems = await menu.visibleMenuItems(); expect(menuItems).toContain('Open'); expect(menuItems).toContain('Delete'); - expect(menuItems).toContain('Download'); + if (!app.isElectron) { + expect(menuItems).toContain('Download'); + } await menu.close(); expect(await menu.isOpen()).toBe(false); @@ -186,4 +201,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 cc305bed2696d..f00fcb6727270 100644 --- a/examples/playwright/src/tests/theia-main-menu.test.ts +++ b/examples/playwright/src/tests/theia-main-menu.test.ts @@ -14,21 +14,27 @@ // 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 { TheiaAppLoader } from '../theia-app-loader'; +import { TheiaAboutDialog } from '../theia-about-dialog'; import { TheiaMenuBar } from '../theia-main-menu'; -import test, { page } from './fixtures/theia-fixture'; - -let menuBar: TheiaMenuBar; +import { OSUtil } from '../util'; test.describe('Theia Main Menu', () => { - test.beforeAll(async () => { - const app = await TheiaApp.load(page); + let app: TheiaApp; + let menuBar: TheiaMenuBar; + + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); 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'); @@ -57,7 +63,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' : app.isElectron ? 'Ctrl+N' : 'Alt+N'); const hasSubmenu = await menuItem?.hasSubmenu(); expect(hasSubmenu).toBe(false); @@ -86,4 +92,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 = 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); + 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 6906ed503be9d..ff741224124e4 100644 --- a/examples/playwright/src/tests/theia-output-view.test.ts +++ b/examples/playwright/src/tests/theia-output-view.test.ts @@ -14,20 +14,21 @@ // 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.afterAll(async () => { + await app.page.close(); }); test('should open the output view and check if is visible and active', async () => { @@ -82,4 +83,3 @@ test.describe('Theia Output View', () => { }); }); - diff --git a/examples/playwright/src/tests/theia-preference-view.test.ts b/examples/playwright/src/tests/theia-preference-view.test.ts index f6e3491c4bab0..bdaf34266ebe6 100644 --- a/examples/playwright/src/tests/theia-preference-view.test.ts +++ b/examples/playwright/src/tests/theia-preference-view.test.ts @@ -14,17 +14,21 @@ // 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 { DefaultPreferences, PreferenceIds, TheiaPreferenceView } from '../theia-preference-view'; -import test, { page } from './fixtures/theia-fixture'; - -let app: TheiaApp; test.describe('Preference View', () => { - test.beforeAll(async () => { - app = await TheiaApp.load(page); + let app: TheiaApp; + + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); + }); + + 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..611080dc6516b 100644 --- a/examples/playwright/src/tests/theia-problems-view.test.ts +++ b/examples/playwright/src/tests/theia-problems-view.test.ts @@ -14,17 +14,21 @@ // 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 { TheiaProblemsView } from '../theia-problem-view'; -import test, { page } from './fixtures/theia-fixture'; - -let app: TheiaApp; test.describe('Theia Problems View', () => { - test.beforeAll(async () => { - app = await TheiaApp.load(page); + let app: TheiaApp; + + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); + }); + + 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..4f9b8352cc4b6 100644 --- a/examples/playwright/src/tests/theia-quick-command.test.ts +++ b/examples/playwright/src/tests/theia-quick-command.test.ts @@ -14,25 +14,29 @@ // 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 { TheiaAppLoader } 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; test.describe('Theia Quick Command', () => { - test.beforeAll(async () => { - app = await TheiaApp.load(page); + 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('should show quick command palette', async () => { await quickCommand.open(); expect(await quickCommand.isOpen()).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 356d80f454662..48dd024d3abfb 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 { TheiaAppLoader } 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,16 @@ class TheiaSampleApp extends TheiaApp { } } -let app: TheiaSampleApp; - test.describe('Theia Sample Application', () => { - test('should load', async () => { - app = await TheiaApp.loadApp(page, TheiaSampleApp); + let app: TheiaSampleApp; + + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }, 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..871e7c468519e 100644 --- a/examples/playwright/src/tests/theia-status-bar.test.ts +++ b/examples/playwright/src/tests/theia-status-bar.test.ts @@ -14,23 +14,28 @@ // 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 { 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; test.describe('Theia Status Bar', () => { - test.beforeAll(async () => { - const app = await TheiaApp.load(page); + let app: TheiaApp; + let statusBar: TheiaStatusBar; + + test.beforeAll(async ({ playwright, browser }) => { + app = await TheiaAppLoader.load({ playwright, browser }); 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-terminal-view.test.ts b/examples/playwright/src/tests/theia-terminal-view.test.ts index 807e35a3c9e15..be4840aaf7315 100644 --- a/examples/playwright/src/tests/theia-terminal-view.test.ts +++ b/examples/playwright/src/tests/theia-terminal-view.test.ts @@ -14,19 +14,23 @@ // 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.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 d47daf09bb129..395357d99e6da 100644 --- a/examples/playwright/src/tests/theia-text-editor.test.ts +++ b/examples/playwright/src/tests/theia-text-editor.test.ts @@ -14,20 +14,20 @@ // 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 { TheiaAppLoader } 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; test.describe('Theia Text Editor', () => { - test.beforeAll(async () => { + let app: TheiaApp; + + 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); // set auto-save preference to off const preferenceView = await app.openPreferences(TheiaPreferenceView); @@ -35,6 +35,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-toolbar.test.ts b/examples/playwright/src/tests/theia-toolbar.test.ts index 1d5234c09208d..d7ea27a7c77c4 100644 --- a/examples/playwright/src/tests/theia-toolbar.test.ts +++ b/examples/playwright/src/tests/theia-toolbar.test.ts @@ -14,21 +14,25 @@ // 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); }); + 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 02ae0f94c4f9b..273deeb608e82 100644 --- a/examples/playwright/src/tests/theia-workspace.test.ts +++ b/examples/playwright/src/tests/theia-workspace.test.ts @@ -14,34 +14,41 @@ // 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 { TheiaAppLoader } 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', () => { + let isElectron: boolean; + test.beforeAll(async ({ playwright, browser }) => { + isElectron = process.env.USE_ELECTRON === 'true'; + }); - test('should be initialized empty by default', async () => { - const app = await TheiaApp.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 empty by default', async ({ playwright, browser }) => { + if (!isElectron) { + 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); + await app.page.close(); + } }); - test('should be initialized with the contents of a file location', async () => { + 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 TheiaApp.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); 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 () => { + 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 TheiaApp.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); @@ -49,6 +56,25 @@ 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 = app.isElectron ? 'Open File...' : '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.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 new file mode 100644 index 0000000000000..08b9084c5d3ba --- /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-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 } from './theia-app'; +import { TheiaWorkspace } from './theia-workspace'; +import { OSUtil } from './util'; + +export interface TheiaAppFactory { + 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`: +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; +} + +function initializeWorkspace(initialWorkspace?: TheiaWorkspace): TheiaWorkspace { + const workspace = initialWorkspace ? initialWorkspace : new TheiaWorkspace(); + workspace.initialize(); + return workspace; +} + +namespace TheiaBrowserAppLoader { + + export async function load( + page: Page, + initialWorkspace?: TheiaWorkspace, + factory?: TheiaAppFactory + ): Promise { + const workspace = initializeWorkspace(initialWorkspace); + return createAndLoad(page, workspace, factory); + } + + async function createAndLoad( + page: Page, + workspace: TheiaWorkspace, + factory?: TheiaAppFactory + ): Promise { + const appFactory = theiaAppFactory(factory); + const app = new appFactory(page, workspace, false); + await loadOrReload(app, '/#' + app.workspace.urlEncodedPath); + await app.waitForShellAndInitialized(); + return app; + } + + async function 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(); + } + } + } +} + +namespace TheiaElectronAppLoader { + + export async function load( + args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs, + initialWorkspace?: TheiaWorkspace, + factory?: TheiaAppFactory, + ): Promise { + const workspace = initializeWorkspace(initialWorkspace); + 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`'); + } + const appPath = electronConfig.electronAppPath!; + const pluginsPath = electronConfig.pluginsPath; + const launchOptions = electronConfig.launchOptions ?? { + additionalArgs: ['--no-cluster'], + electronAppPath: appPath, + pluginsPath: pluginsPath + }; + 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; + } + + export function toPlaywrightOptions( + electronLaunchOptions: { additionalArgs: string[], electronAppPath: string, pluginsPath?: string } | object, + workspace?: TheiaWorkspace + ): { 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); + } + + return { executablePath: executablePath, args: args }; + } + return electronLaunchOptions; + } +} + +export namespace TheiaAppLoader { + + export async function load( + args: TheiaPlaywrightTestConfig & PlaywrightWorkerArgs, + initialWorkspace?: TheiaWorkspace, + 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(); + return TheiaBrowserAppLoader.load(page, initialWorkspace, factory); + } +} diff --git a/examples/playwright/src/theia-app.ts b/examples/playwright/src/theia-app.ts index 901a7a172dbc2..ab7aeba5e71e9 100644 --- a/examples/playwright/src/theia-app.ts +++ b/examples/playwright/src/theia-app.ts @@ -37,47 +37,44 @@ export const DefaultTheiaAppData: TheiaAppData = { 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; + + protected appData = DefaultTheiaAppData; + + public constructor( + public page: Page, + public workspace: TheiaWorkspace, + public isElectron: boolean, + ) { + this.statusBar = this.createStatusBar(); + this.quickCommandPalette = this.createQuickCommandPalette(); + this.menuBar = this.createMenuBar(); + } + + protected createStatusBar(): TheiaStatusBar { + return new TheiaStatusBar(this); + } - static async load(page: Page, initialWorkspace?: TheiaWorkspace): Promise { - return this.loadApp(page, TheiaApp, initialWorkspace); + protected createQuickCommandPalette(): TheiaQuickCommandPalette { + return new TheiaQuickCommandPalette(this); } - 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; + protected createMenuBar(): TheiaMenuBar { + return new TheiaMenuBar(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 { 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-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/core/src/electron-main/electron-main-application.ts b/packages/core/src/electron-main/electron-main-application.ts index c1394d5d06d6e..3a67acd71ff1b 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'; } @@ -273,7 +277,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 }); 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..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,8 +15,8 @@ // ***************************************************************************** import { ContainerModule } from '@theia/core/shared/inversify'; -import { FileDialogService } from '../../browser/file-dialog/file-dialog-service'; import { ElectronFileDialogService } from './electron-file-dialog-service'; +import { FileDialogService } from '../../browser'; export default new ContainerModule((bind, _unbind, _isBound, rebind) => { bind(ElectronFileDialogService).toSelf().inSingletonScope(); 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..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,40 +41,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.electronTheiaCore.useNativeElements) { + 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.electronTheiaCore.useNativeElements) { + 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 { diff --git a/packages/filesystem/src/electron-browser/preload.ts b/packages/filesystem/src/electron-browser/preload.ts index 7a5f0d3ffd400..32c2290c6120f 100644 --- a/packages/filesystem/src/electron-browser/preload.ts +++ b/packages/filesystem/src/electron-browser/preload.ts @@ -27,4 +27,5 @@ export function preload(): void { console.log('exposing theia filesystem electron api'); contextBridge.exposeInMainWorld('electronTheiaFilesystem', api); + }