diff --git a/.eslintrc.js b/.eslintrc.js
index 33e3b4a..23f3a72 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -7,14 +7,6 @@ module.exports = {
tsconfigRootDir: __dirname,
project: 'tsconfig.eslint.json'
},
- settings: {
- // Necessary for aliasing paths: https://www.typescriptlang.org/tsconfig#paths
- 'import/resolver': {
- typescript: {
- project: ['packages/glsp-playwright/tsconfig.json', 'tsconfig.json']
- }
- }
- },
rules: {
'no-null/no-null': 'off', // Accessing the browser DOM returns "null" instead of "undefined"
'no-restricted-imports': [
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1e5b626..212682c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,8 +6,8 @@
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
- "source.fixAll.eslint": true,
- "source.organizeImports": true
+ "source.fixAll.eslint": "explicit",
+ "source.organizeImports": "explicit"
},
"eslint.validate": ["javascript", "typescript"],
"search.exclude": {
diff --git a/examples/workflow-test/.env.example b/examples/workflow-test/.env.example
index dca988a..c7e7ff5 100644
--- a/examples/workflow-test/.env.example
+++ b/examples/workflow-test/.env.example
@@ -1,6 +1,6 @@
-# For Theia and VSCode instances
-GLSP_SERVER_DEBUG="true"
-GLSP_SERVER_PORT="8081"
+GLSP_SERVER_DEBUG="true" # For Theia and VSCode instances to connect to an external server
+GLSP_SERVER_PORT="8081"
+GLSP_SERVER_PLAYWRIGHT_MANAGED="true" # To allow Playwright to manage/start the server
GLSP_WEBSOCKET_PATH="workflow"
# Configurations
diff --git a/examples/workflow-test/README.md b/examples/workflow-test/README.md
index 17ea18b..8d47b8c 100644
--- a/examples/workflow-test/README.md
+++ b/examples/workflow-test/README.md
@@ -2,6 +2,40 @@
This package contains code examples that demonstrate how to test diagram editors using the [Graphical Language Server Platform (GLSP)](https://github.com/eclipse-glsp/glsp).
+
+ Expand test list
+
+| Feature | Standalone | Theia Integration | Eclipse Integration | VS Code Integration |
+| ------------------------------------------------------------------------------------ | :------------------: | :---------------: | :-----------------: | :-----------------: |
+| Model Saving | - | - | - | - |
+| Model Dirty State | | - | - | - |
+| Model SVG Export | - | - | - | - |
+| Model Layout | - | - | - | - |
+| Restoring viewport on re-open | | - | | |
+| Model Edit Modes
- Edit
- Read-only |
-
- |
-
- |
-
|
-
- |
+| Client View Port
- Center
- Fit to Screen |
-
- |
-
- |
-
- |
-
- |
+| Client Status Notification | - | - | - | - |
+| Client Message Notification | - | - | | - |
+| Client Progress Reporting | | - | | - |
+| Element Selection | ✓ | ✓ | - | ✓ |
+| Element Hover | - | - | - | - |
+| Element Validation | - | - | - | - |
+| Element Navigation | | - | - | - |
+| Element Type Hints | - | - | - | - |
+| Element Creation and Deletion | - | - | - | - |
+| Node Change Bounds
- Move
- Resize |
-
- |
-
- |
-
- |
-
- |
+| Node Change Container | - | - | - | - |
+| Edge Reconnect | - | - | - | - |
+| Edge Routing Points | - | - | - | - |
+| Ghost Elements | - | - | - | - |
+| Element Text Editing | - | - | - | - |
+| Clipboard (Cut, Copy, Paste) | - | - | - | - |
+| Undo / Redo | - | - | - | - |
+| Contexts
- Context Menu
- Command Palette
- Tool Palette |
-
- |
-
-
- |
-
- |
-
-
- |
+| Accessibility Features (experimental)
- Search
- Move
- Zoom
- Resize |
-
-
-
- | | | |
+| Helper Lines (experimental) | - | - | - | - |
+
+
## Prerequisites
The following libraries/frameworks need to be installed on your system:
diff --git a/examples/workflow-test/configs/project.config.ts b/examples/workflow-test/configs/project.config.ts
new file mode 100644
index 0000000..87760ad
--- /dev/null
+++ b/examples/workflow-test/configs/project.config.ts
@@ -0,0 +1,118 @@
+/********************************************************************************
+ * Copyright (c) 2024 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 {
+ GLSPPlaywrightOptions,
+ StandaloneIntegrationOptions,
+ TheiaIntegrationOptions,
+ VSCodeIntegrationOptions
+} from '@eclipse-glsp/glsp-playwright';
+import { PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, Project, devices } from '@playwright/test';
+import * as path from 'path';
+import { getEnv } from './utils';
+
+const projectDevices = devices['Desktop Chrome'];
+
+export function createStandaloneProject(): Project[] {
+ const url = getEnv('STANDALONE_URL');
+
+ if (url === undefined) {
+ console.error(`[Worker: ${process.env.TEST_PARALLEL_INDEX}] Standalone project can not be created.\n`);
+ return [];
+ }
+
+ const standaloneIntegrationOptions: StandaloneIntegrationOptions = {
+ type: 'Standalone',
+ url
+ };
+
+ return [
+ {
+ name: 'standalone',
+ testMatch: ['**/*.spec.js'],
+ use: {
+ ...projectDevices,
+ integrationOptions: standaloneIntegrationOptions
+ }
+ }
+ ];
+}
+
+export function createTheiaProject(): Project[] {
+ const url = getEnv('THEIA_URL');
+
+ if (url === undefined) {
+ console.error(`[Worker: ${process.env.TEST_PARALLEL_INDEX}] Theia project can not be created.\n`);
+ return [];
+ }
+
+ const theiaIntegrationOptions: TheiaIntegrationOptions = {
+ type: 'Theia',
+ url,
+ widgetId: 'workflow-diagram',
+ workspace: '../workspace',
+ file: 'example1.wf'
+ };
+
+ return [
+ {
+ name: 'theia',
+ testMatch: ['**/*.spec.js'],
+ use: {
+ ...projectDevices,
+ baseURL: theiaIntegrationOptions.url,
+ integrationOptions: theiaIntegrationOptions
+ }
+ }
+ ];
+}
+
+export function createVSCodeProject(): Project[] {
+ const vsixId = getEnv('VSCODE_VSIX_ID');
+ const vsixPath = getEnv('VSCODE_VSIX_PATH');
+
+ if (vsixId === undefined || vsixPath === undefined) {
+ console.error(`[Worker: ${process.env.TEST_PARALLEL_INDEX}] VSCode project can not be created.\n`);
+ return [];
+ }
+
+ const vscodeIntegrationOptions: VSCodeIntegrationOptions = {
+ type: 'VSCode',
+ workspace: '../workspace',
+ file: 'example1.wf',
+ vsixId,
+ vsixPath,
+ storagePath: path.join(__dirname, '../playwright/.storage/vscode.setup.json')
+ };
+
+ return [
+ {
+ name: 'vscode-setup',
+ testMatch: ['setup/vscode.setup.js'],
+ use: {
+ integrationOptions: vscodeIntegrationOptions
+ }
+ },
+ {
+ name: 'vscode',
+ testMatch: ['**/*.spec.js'],
+ dependencies: ['vscode-setup'],
+ use: {
+ integrationOptions: vscodeIntegrationOptions
+ }
+ }
+ ];
+}
diff --git a/examples/workflow-test/src/utils.ts b/examples/workflow-test/configs/utils.ts
similarity index 69%
rename from examples/workflow-test/src/utils.ts
rename to examples/workflow-test/configs/utils.ts
index 06e9808..3ec8e21 100644
--- a/examples/workflow-test/src/utils.ts
+++ b/examples/workflow-test/configs/utils.ts
@@ -1,5 +1,5 @@
/********************************************************************************
- * Copyright (c) 2023 EclipseSource and others.
+ * Copyright (c) 2024 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
@@ -14,9 +14,11 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-export function getDefined(val?: string): string {
- if (val === undefined || val === null) {
- throw new Error('Given value is not defined');
+export function getEnv(parameter: string, log: boolean = true): string | undefined {
+ const val = process.env[parameter];
+
+ if (log && (val === undefined || val === null)) {
+ console.error(`[Worker: ${process.env.TEST_PARALLEL_INDEX}] Parameter "${parameter}" not found in process.env`);
}
return val;
}
diff --git a/examples/workflow-test/configs/webserver.config.ts b/examples/workflow-test/configs/webserver.config.ts
new file mode 100644
index 0000000..6075bec
--- /dev/null
+++ b/examples/workflow-test/configs/webserver.config.ts
@@ -0,0 +1,53 @@
+/********************************************************************************
+ * Copyright (c) 2024 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 { GLSPPlaywrightOptions } from '@eclipse-glsp/glsp-playwright';
+import { PlaywrightTestConfig } from '@playwright/test';
+import { getEnv } from './utils';
+
+export function isManagedServer(): boolean {
+ const env = getEnv('GLSP_SERVER_PLAYWRIGHT_MANAGED', false);
+ return env === undefined || env === 'true';
+}
+
+export function hasRunningServer(config: PlaywrightTestConfig): boolean {
+ const webserver = config.webServer;
+
+ const isArray = Array.isArray(webserver);
+ return !isManagedServer() || (isArray && webserver.length > 0) || (!isArray && webserver !== undefined);
+}
+
+export function createWebserver(): PlaywrightTestConfig['webServer'] {
+ if (!isManagedServer()) {
+ return [];
+ }
+
+ const port = getEnv('GLSP_SERVER_PORT');
+
+ if (port === undefined) {
+ console.error('Webserver will be not created.\n');
+ return [];
+ }
+
+ return [
+ {
+ command: `yarn start:server -w -p ${+port}`,
+ port: +port,
+ reuseExistingServer: !process.env.CI,
+ stdout: 'ignore'
+ }
+ ];
+}
diff --git a/examples/workflow-test/package.json b/examples/workflow-test/package.json
index 2332ca1..3cea89a 100644
--- a/examples/workflow-test/package.json
+++ b/examples/workflow-test/package.json
@@ -47,11 +47,8 @@
"devDependencies": {
"@eclipse-glsp-examples/workflow-server-bundled": "~2.0.1",
"@eclipse-glsp/glsp-playwright": "~2.0.0",
- "@playwright/test": "^1.34.3",
- "@theia/playwright": "1.39.0",
- "commander": "^10.0.1",
+ "@playwright/test": "^1.40.1",
"dotenv": "^16.0.3",
- "mvn-artifact-download": "5.1.0",
"ts-dedent": "^2.2.0",
"tsx": "^3.12.4"
},
diff --git a/examples/workflow-test/playwright.config.ts b/examples/workflow-test/playwright.config.ts
index 2894858..2791d40 100644
--- a/examples/workflow-test/playwright.config.ts
+++ b/examples/workflow-test/playwright.config.ts
@@ -15,42 +15,14 @@
********************************************************************************/
import 'reflect-metadata';
-import type {
- GLSPPlaywrightOptions,
- StandaloneIntegrationOptions,
- TheiaIntegrationOptions,
- VSCodeIntegrationOptions
-} from '@eclipse-glsp/glsp-playwright';
+import type { GLSPPlaywrightOptions } from '@eclipse-glsp/glsp-playwright';
import type { PlaywrightTestConfig } from '@playwright/test';
-import { devices } from '@playwright/test';
import * as dotenv from 'dotenv';
-import * as path from 'path';
-import { getDefined } from './src/utils';
+import { createStandaloneProject, createTheiaProject, createVSCodeProject } from './configs/project.config';
+import { createWebserver, hasRunningServer } from './configs/webserver.config';
dotenv.config();
-const standaloneIntegrationOptions: StandaloneIntegrationOptions = {
- type: 'Standalone',
- url: getDefined(process.env.STANDALONE_URL)
-};
-
-const theiaIntegrationOptions: TheiaIntegrationOptions = {
- type: 'Theia',
- url: getDefined(process.env.THEIA_URL),
- widgetId: 'workflow-diagram',
- workspace: '../workspace',
- file: 'example1.wf'
-};
-
-const vscodeIntegrationOptions: VSCodeIntegrationOptions = {
- type: 'VSCode',
- workspace: '../workspace',
- file: 'example1.wf',
- vsixId: getDefined(process.env.VSCODE_VSIX_ID),
- vsixPath: getDefined(process.env.VSCODE_VSIX_PATH),
- storagePath: path.join(__dirname, 'playwright/.storage/vscode.setup.json')
-};
-
/**
* See https://playwright.dev/docs/test-configuration.
*/
@@ -69,48 +41,12 @@ const config: PlaywrightTestConfig = {
actionTimeout: 0,
trace: 'on-first-retry'
},
- webServer: [
- {
- command: `yarn start:server -w -p ${+getDefined(process.env.GLSP_SERVER_PORT)}`,
- port: +getDefined(process.env.GLSP_SERVER_PORT),
- reuseExistingServer: !process.env.CI,
- stdout: 'ignore'
- }
- ],
- projects: [
- {
- name: 'standalone',
- testMatch: ['**/*.spec.js'],
- use: {
- ...devices['Desktop Chrome'],
- integrationOptions: standaloneIntegrationOptions
- }
- },
- {
- name: 'theia',
- testMatch: ['**/*.spec.js'],
- use: {
- ...devices['Desktop Chrome'],
- baseURL: theiaIntegrationOptions.url,
- integrationOptions: theiaIntegrationOptions
- }
- },
- {
- name: 'vscode-setup',
- testMatch: ['setup/vscode.setup.js'],
- use: {
- integrationOptions: vscodeIntegrationOptions
- }
- },
- {
- name: 'vscode',
- testMatch: ['**/*.spec.js'],
- dependencies: ['vscode-setup'],
- use: {
- integrationOptions: vscodeIntegrationOptions
- }
- }
- ]
+ webServer: createWebserver(),
+ projects: []
};
+if (hasRunningServer(config)) {
+ config.projects = [...createStandaloneProject(), ...createTheiaProject(), ...createVSCodeProject()];
+}
+
export default config;
diff --git a/examples/workflow-test/src/graph/elements/task-automated.po.ts b/examples/workflow-test/src/graph/elements/task-automated.po.ts
index 40b1181..da372b2 100644
--- a/examples/workflow-test/src/graph/elements/task-automated.po.ts
+++ b/examples/workflow-test/src/graph/elements/task-automated.po.ts
@@ -16,7 +16,9 @@
import {
ChildrenAccessor,
Mix,
- NodeMetadata, PNode, SVGMetadataUtils,
+ NodeMetadata,
+ PNode,
+ SVGMetadataUtils,
useClickableFlow,
useCommandPaletteCapability,
useDraggableFlow,
diff --git a/examples/workflow-test/src/graph/elements/task-manual.po.ts b/examples/workflow-test/src/graph/elements/task-manual.po.ts
index 08263c0..d4ac1b1 100644
--- a/examples/workflow-test/src/graph/elements/task-manual.po.ts
+++ b/examples/workflow-test/src/graph/elements/task-manual.po.ts
@@ -18,7 +18,8 @@ import {
Mix,
NodeMetadata,
PLabelledElement,
- PNode, SVGMetadataUtils,
+ PNode,
+ SVGMetadataUtils,
useClickableFlow,
useCommandPaletteCapability,
useDeletableFlow,
@@ -26,7 +27,8 @@ import {
useHoverableFlow,
usePopupCapability,
useRenameableFlow,
- useResizeHandleCapability
+ useResizeHandleCapability,
+ useSelectableFlow
} from '@eclipse-glsp/glsp-playwright/';
import { LabelHeading } from './label-heading.po';
@@ -36,6 +38,7 @@ export const TaskManualMixin = Mix(PNode)
.flow(useDeletableFlow)
.flow(useDraggableFlow)
.flow(useRenameableFlow)
+ .flow(useSelectableFlow)
.capability(useResizeHandleCapability)
.capability(usePopupCapability)
.capability(useCommandPaletteCapability)
diff --git a/examples/workflow-test/tests/features/connectable-element.spec.ts b/examples/workflow-test/tests/core/connectable-element.spec.ts
similarity index 96%
rename from examples/workflow-test/tests/features/connectable-element.spec.ts
rename to examples/workflow-test/tests/core/connectable-element.spec.ts
index f2ac644..76e3149 100644
--- a/examples/workflow-test/tests/features/connectable-element.spec.ts
+++ b/examples/workflow-test/tests/core/connectable-element.spec.ts
@@ -13,7 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, expect, test } from '@eclipse-glsp/glsp-playwright/';
+import { expect, test } from '@eclipse-glsp/glsp-playwright/';
import { WorkflowApp } from '../../src/app/workflow-app';
import { ActivityNodeFork } from '../../src/graph/elements/activity-node-fork.po';
import { Edge } from '../../src/graph/elements/edge.po';
@@ -25,7 +25,7 @@ test.describe('The edge accessor of a connectable element', () => {
let graph: WorkflowGraph;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/edge.spec.ts b/examples/workflow-test/tests/core/edge.spec.ts
similarity index 94%
rename from examples/workflow-test/tests/features/edge.spec.ts
rename to examples/workflow-test/tests/core/edge.spec.ts
index cd19dd7..2563f36 100644
--- a/examples/workflow-test/tests/features/edge.spec.ts
+++ b/examples/workflow-test/tests/core/edge.spec.ts
@@ -13,7 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, expect, test } from '@eclipse-glsp/glsp-playwright/';
+import { expect, test } from '@eclipse-glsp/glsp-playwright/';
import { WorkflowApp } from '../../src/app/workflow-app';
import { ActivityNodeFork } from '../../src/graph/elements/activity-node-fork.po';
import { Edge } from '../../src/graph/elements/edge.po';
@@ -25,7 +25,7 @@ test.describe('Edges', () => {
let graph: WorkflowGraph;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/flows/deletable.spec.ts b/examples/workflow-test/tests/core/flows/deletable.spec.ts
similarity index 93%
rename from examples/workflow-test/tests/features/flows/deletable.spec.ts
rename to examples/workflow-test/tests/core/flows/deletable.spec.ts
index a6e2eb7..133f946 100644
--- a/examples/workflow-test/tests/features/flows/deletable.spec.ts
+++ b/examples/workflow-test/tests/core/flows/deletable.spec.ts
@@ -13,7 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, expect, test } from '@eclipse-glsp/glsp-playwright/';
+import { expect, test } from '@eclipse-glsp/glsp-playwright/';
import { WorkflowApp } from '../../../src/app/workflow-app';
import { TaskManual } from '../../../src/graph/elements/task-manual.po';
import { WorkflowGraph } from '../../../src/graph/workflow.graph';
@@ -23,7 +23,7 @@ test.describe('Deletable flow', () => {
let graph: WorkflowGraph;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/graph.spec.ts b/examples/workflow-test/tests/core/graph.spec.ts
similarity index 97%
rename from examples/workflow-test/tests/features/graph.spec.ts
rename to examples/workflow-test/tests/core/graph.spec.ts
index 63d3e99..40bc9bf 100644
--- a/examples/workflow-test/tests/features/graph.spec.ts
+++ b/examples/workflow-test/tests/core/graph.spec.ts
@@ -13,7 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, expect, test } from '@eclipse-glsp/glsp-playwright/';
+import { expect, test } from '@eclipse-glsp/glsp-playwright/';
import { WorkflowApp } from '../../src/app/workflow-app';
import { ActivityNodeFork } from '../../src/graph/elements/activity-node-fork.po';
import { Edge } from '../../src/graph/elements/edge.po';
@@ -25,7 +25,7 @@ test.describe('The graph', () => {
let graph: WorkflowGraph;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/parent.spec.ts b/examples/workflow-test/tests/core/parent.spec.ts
similarity index 95%
rename from examples/workflow-test/tests/features/parent.spec.ts
rename to examples/workflow-test/tests/core/parent.spec.ts
index e34f526..ee7a0e1 100644
--- a/examples/workflow-test/tests/features/parent.spec.ts
+++ b/examples/workflow-test/tests/core/parent.spec.ts
@@ -13,7 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, expect, test } from '@eclipse-glsp/glsp-playwright/';
+import { expect, test } from '@eclipse-glsp/glsp-playwright/';
import { WorkflowApp } from '../../src/app/workflow-app';
import { LabelHeading } from '../../src/graph/elements/label-heading.po';
import { TaskManual } from '../../src/graph/elements/task-manual.po';
@@ -24,7 +24,7 @@ test.describe('The children accessor of a parent element', () => {
let graph: WorkflowGraph;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/shortcuts.spec.ts b/examples/workflow-test/tests/core/shortcuts.spec.ts
similarity index 93%
rename from examples/workflow-test/tests/features/shortcuts.spec.ts
rename to examples/workflow-test/tests/core/shortcuts.spec.ts
index 6647694..03edefc 100644
--- a/examples/workflow-test/tests/features/shortcuts.spec.ts
+++ b/examples/workflow-test/tests/core/shortcuts.spec.ts
@@ -13,7 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, expect, test } from '@eclipse-glsp/glsp-playwright';
+import { expect, test } from '@eclipse-glsp/glsp-playwright';
import { WorkflowApp } from '../../src/app/workflow-app';
import { TaskManual } from '../../src/graph/elements/task-manual.po';
import { WorkflowGraph } from '../../src/graph/workflow.graph';
@@ -23,7 +23,7 @@ test.describe('Shortcuts', () => {
let graph: WorkflowGraph;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/resize-handle.spec.ts b/examples/workflow-test/tests/features/change-bounds/resize-handle.spec.ts
similarity index 89%
rename from examples/workflow-test/tests/features/resize-handle.spec.ts
rename to examples/workflow-test/tests/features/change-bounds/resize-handle.spec.ts
index 4155b0e..6ed887b 100644
--- a/examples/workflow-test/tests/features/resize-handle.spec.ts
+++ b/examples/workflow-test/tests/features/change-bounds/resize-handle.spec.ts
@@ -13,17 +13,17 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, PMetadata, ResizeHandle, expect, test } from '@eclipse-glsp/glsp-playwright';
-import { WorkflowApp } from '../../src/app/workflow-app';
-import { TaskManual } from '../../src/graph/elements/task-manual.po';
-import { WorkflowGraph } from '../../src/graph/workflow.graph';
+import { PMetadata, ResizeHandle, expect, test } from '@eclipse-glsp/glsp-playwright';
+import { WorkflowApp } from '../../../src/app/workflow-app';
+import { TaskManual } from '../../../src/graph/elements/task-manual.po';
+import { WorkflowGraph } from '../../../src/graph/workflow.graph';
test.describe('The resizing handle', () => {
let app: WorkflowApp;
let graph: WorkflowGraph;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/command-palette.spec.ts b/examples/workflow-test/tests/features/command-palette/command-palette.spec.ts
similarity index 94%
rename from examples/workflow-test/tests/features/command-palette.spec.ts
rename to examples/workflow-test/tests/features/command-palette/command-palette.spec.ts
index 42a1ac1..43a7430 100644
--- a/examples/workflow-test/tests/features/command-palette.spec.ts
+++ b/examples/workflow-test/tests/features/command-palette/command-palette.spec.ts
@@ -13,12 +13,12 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, GLSPGlobalCommandPalette, expect, test } from '@eclipse-glsp/glsp-playwright/';
-import { WorkflowApp } from '../../src/app/workflow-app';
-import { Edge } from '../../src/graph/elements/edge.po';
-import { TaskAutomated } from '../../src/graph/elements/task-automated.po';
-import { TaskManual } from '../../src/graph/elements/task-manual.po';
-import { WorkflowGraph } from '../../src/graph/workflow.graph';
+import { GLSPGlobalCommandPalette, expect, test } from '@eclipse-glsp/glsp-playwright/';
+import { WorkflowApp } from '../../../src/app/workflow-app';
+import { Edge } from '../../../src/graph/elements/edge.po';
+import { TaskAutomated } from '../../../src/graph/elements/task-automated.po';
+import { TaskManual } from '../../../src/graph/elements/task-manual.po';
+import { WorkflowGraph } from '../../../src/graph/workflow.graph';
test.describe('The command palette', () => {
let app: WorkflowApp;
@@ -26,7 +26,7 @@ test.describe('The command palette', () => {
let globalCommandPalette: GLSPGlobalCommandPalette;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/popup.spec.ts b/examples/workflow-test/tests/features/hover/popup.spec.ts
similarity index 87%
rename from examples/workflow-test/tests/features/popup.spec.ts
rename to examples/workflow-test/tests/features/hover/popup.spec.ts
index 2cf1235..737d329 100644
--- a/examples/workflow-test/tests/features/popup.spec.ts
+++ b/examples/workflow-test/tests/features/hover/popup.spec.ts
@@ -13,18 +13,18 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, expect, test } from '@eclipse-glsp/glsp-playwright/';
+import { expect, test } from '@eclipse-glsp/glsp-playwright/';
import { dedent } from 'ts-dedent';
-import { WorkflowApp } from '../../src/app/workflow-app';
-import { TaskManual } from '../../src/graph/elements/task-manual.po';
-import { WorkflowGraph } from '../../src/graph/workflow.graph';
+import { WorkflowApp } from '../../../src/app/workflow-app';
+import { TaskManual } from '../../../src/graph/elements/task-manual.po';
+import { WorkflowGraph } from '../../../src/graph/workflow.graph';
test.describe('The popup', () => {
let app: WorkflowApp;
let graph: WorkflowGraph;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/routing-point.spec.ts b/examples/workflow-test/tests/features/routing/routing-point.spec.ts
similarity index 88%
rename from examples/workflow-test/tests/features/routing-point.spec.ts
rename to examples/workflow-test/tests/features/routing/routing-point.spec.ts
index 55c124c..388bfb5 100644
--- a/examples/workflow-test/tests/features/routing-point.spec.ts
+++ b/examples/workflow-test/tests/features/routing/routing-point.spec.ts
@@ -13,17 +13,17 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, expect, test } from '@eclipse-glsp/glsp-playwright/';
-import { WorkflowApp } from '../../src/app/workflow-app';
-import { Edge } from '../../src/graph/elements/edge.po';
-import { WorkflowGraph } from '../../src/graph/workflow.graph';
+import { expect, test } from '@eclipse-glsp/glsp-playwright/';
+import { WorkflowApp } from '../../../src/app/workflow-app';
+import { Edge } from '../../../src/graph/elements/edge.po';
+import { WorkflowGraph } from '../../../src/graph/workflow.graph';
test.describe('The routing points of an edge', () => {
let app: WorkflowApp;
let graph: WorkflowGraph;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/select/select.spec.ts b/examples/workflow-test/tests/features/select/select.spec.ts
new file mode 100644
index 0000000..74b0c86
--- /dev/null
+++ b/examples/workflow-test/tests/features/select/select.spec.ts
@@ -0,0 +1,118 @@
+/********************************************************************************
+ * Copyright (c) 2024 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 { PModelElement, Selectable, expect, skipIntegration, test } from '@eclipse-glsp/glsp-playwright';
+import { WorkflowApp } from '../../../src/app/workflow-app';
+import { TaskManual } from '../../../src/graph/elements/task-manual.po';
+import { WorkflowGraph } from '../../../src/graph/workflow.graph';
+
+test.describe('The select feature', () => {
+ let app: WorkflowApp;
+ let graph: WorkflowGraph;
+
+ test.beforeEach(async ({ integration }) => {
+ app = new WorkflowApp({
+ type: 'integration',
+ integration
+ });
+ graph = app.graph;
+ });
+
+ test('should allow to select a single element', async () => {
+ const element = await graph.getNodeByLabel('Push', TaskManual);
+ await element.select();
+
+ const selectedElement = await graph.getNodeBySelector(`.${Selectable.CSS}`, TaskManual);
+
+ expect(await selectedElement.idAttr()).toBe(await element.idAttr());
+ });
+
+ test('should deselect after a new selection', async () => {
+ const element1 = await graph.getNodeByLabel('Push', TaskManual);
+ await element1.select();
+
+ const selectedElement = await graph.getNodeBySelector(`.${Selectable.CSS}`, TaskManual);
+ expect(await selectedElement.idAttr()).toBe(await element1.idAttr());
+
+ const element2 = await graph.getNodeByLabel('RflWt', TaskManual);
+ await element2.select();
+
+ const selectedElements = await graph.getNodesBySelector(`.${Selectable.CSS}`, TaskManual);
+ expect(selectedElements).toHaveLength(1);
+ expect(await selectedElements[0].idAttr()).toBe(await element2.idAttr());
+ });
+
+ test('should allow to select multiple elements', async () => {
+ const element1 = await graph.getNodeByLabel('Push', TaskManual);
+ await element1.select();
+
+ const selectedElement = await graph.getNodeBySelector(`.${Selectable.CSS}`, TaskManual);
+ expect(await selectedElement.idAttr()).toBe(await element1.idAttr());
+
+ const element2 = await graph.getNodeByLabel('RflWt', TaskManual);
+ await element2.select({ modifiers: ['Control'] });
+
+ const targetIds = [await element1.idAttr(), await element2.idAttr()];
+ const expectIds = await Promise.all((await graph.getNodesBySelector(`.${Selectable.CSS}`, TaskManual)).map(e => e.idAttr()));
+ expect(expectIds).toStrictEqual(targetIds);
+ });
+
+ test('should allow to select all elements by using a shortcut', async () => {
+ const page = app.page;
+ await graph.locate().click();
+ await page.keyboard.press('Control+A');
+ const elements = await graph.getAllModelElements();
+
+ for (const element of elements) {
+ expect(await element.classAttr()).toContain(Selectable.CSS);
+ }
+ });
+
+ test.describe('', () => {
+ test.skip(({ integrationOptions }) => skipIntegration(integrationOptions, 'Theia', 'VSCode'), 'Not supported');
+
+ test('should allow to deselect a single element through a keybinding', async () => {
+ const page = app.page;
+ const element = await graph.getNodeByLabel('Push', TaskManual);
+ await element.select();
+ const before = await graph.getNodesBySelector(`.${Selectable.CSS}`, TaskManual);
+ expect(before).toHaveLength(1);
+
+ // Resize Handle
+ await page.keyboard.press('Escape');
+ // Selection
+ await page.keyboard.press('Escape');
+
+ await Promise.all((await graph.locate().locator(`.${Selectable.CSS}`).all()).map(l => l.waitFor({ state: 'detached' })));
+
+ const after = await graph.getModelElementsBySelector(`.${Selectable.CSS}`, PModelElement);
+ expect(after).toHaveLength(0);
+ });
+ });
+
+ test('should allow to deselect a single element by clicking outside', async () => {
+ const element = await graph.getNodeByLabel('Push', TaskManual);
+ await element.select();
+ const before = await graph.getNodesBySelector(`.${Selectable.CSS}`, TaskManual);
+ expect(before).toHaveLength(1);
+
+ await graph.locate().click();
+ await Promise.all((await graph.locate().locator(`.${Selectable.CSS}`).all()).map(l => l.waitFor({ state: 'detached' })));
+
+ const after = await graph.getModelElementsBySelector(`.${Selectable.CSS}`, PModelElement);
+ expect(after).toHaveLength(0);
+ });
+});
diff --git a/examples/workflow-test/tests/features/tool-palette.spec.ts b/examples/workflow-test/tests/features/tool-palette/tool-palette.spec.ts
similarity index 92%
rename from examples/workflow-test/tests/features/tool-palette.spec.ts
rename to examples/workflow-test/tests/features/tool-palette/tool-palette.spec.ts
index 0fdb749..11de4bc 100644
--- a/examples/workflow-test/tests/features/tool-palette.spec.ts
+++ b/examples/workflow-test/tests/features/tool-palette/tool-palette.spec.ts
@@ -13,13 +13,13 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, Marker, createParameterizedIntegrationData, expect, test } from '@eclipse-glsp/glsp-playwright';
-import { WorkflowApp } from '../../src/app/workflow-app';
-import { WorkflowToolPalette } from '../../src/features/tool-palette/workflow-tool-palette';
-import { Edge } from '../../src/graph/elements/edge.po';
-import { TaskAutomated } from '../../src/graph/elements/task-automated.po';
-import { TaskManual } from '../../src/graph/elements/task-manual.po';
-import { WorkflowGraph } from '../../src/graph/workflow.graph';
+import { Marker, createParameterizedIntegrationData, expect, test } from '@eclipse-glsp/glsp-playwright';
+import { WorkflowApp } from '../../../src/app/workflow-app';
+import { WorkflowToolPalette } from '../../../src/features/tool-palette/workflow-tool-palette';
+import { Edge } from '../../../src/graph/elements/edge.po';
+import { TaskAutomated } from '../../../src/graph/elements/task-automated.po';
+import { TaskManual } from '../../../src/graph/elements/task-manual.po';
+import { WorkflowGraph } from '../../../src/graph/workflow.graph';
const integrationData = createParameterizedIntegrationData<{
nodes: string;
@@ -43,7 +43,7 @@ test.describe('The tool palette', () => {
let toolPalette: WorkflowToolPalette;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tests/features/marker.spec.ts b/examples/workflow-test/tests/features/validation/marker.spec.ts
similarity index 83%
rename from examples/workflow-test/tests/features/marker.spec.ts
rename to examples/workflow-test/tests/features/validation/marker.spec.ts
index bc07050..9b382dc 100644
--- a/examples/workflow-test/tests/features/marker.spec.ts
+++ b/examples/workflow-test/tests/features/validation/marker.spec.ts
@@ -13,17 +13,17 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { GLSPApp, expect, test } from '@eclipse-glsp/glsp-playwright/';
-import { WorkflowApp } from '../../src/app/workflow-app';
-import { TaskAutomated } from '../../src/graph/elements/task-automated.po';
-import { WorkflowGraph } from '../../src/graph/workflow.graph';
+import { expect, test } from '@eclipse-glsp/glsp-playwright/';
+import { WorkflowApp } from '../../../src/app/workflow-app';
+import { TaskAutomated } from '../../../src/graph/elements/task-automated.po';
+import { WorkflowGraph } from '../../../src/graph/workflow.graph';
test.describe('The marker', () => {
let app: WorkflowApp;
let graph: WorkflowGraph;
test.beforeEach(async ({ integration }) => {
- app = await GLSPApp.loadApp(WorkflowApp, {
+ app = new WorkflowApp({
type: 'integration',
integration
});
diff --git a/examples/workflow-test/tsconfig.json b/examples/workflow-test/tsconfig.json
index be231c8..4d153bc 100644
--- a/examples/workflow-test/tsconfig.json
+++ b/examples/workflow-test/tsconfig.json
@@ -7,5 +7,5 @@
"emitDecoratorMetadata": true,
"baseUrl": "."
},
- "include": ["src", "tests", "scripts", "playwright.config.ts", "./server/server-config.json"]
+ "include": ["src", "tests", "scripts", "playwright.config.ts", "./configs/*.ts", "./server/server-config.json"]
}
diff --git a/packages/glsp-playwright/.eslintrc.js b/packages/glsp-playwright/.eslintrc.js
index 37c2321..77030c1 100644
--- a/packages/glsp-playwright/.eslintrc.js
+++ b/packages/glsp-playwright/.eslintrc.js
@@ -1,5 +1,13 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: '../../.eslintrc.js',
+ settings: {
+ // Necessary for aliasing paths: https://www.typescriptlang.org/tsconfig#paths
+ 'import/resolver': {
+ typescript: {
+ project: ['packages/glsp-playwright/tsconfig.json', 'tsconfig.json']
+ }
+ }
+ },
rules: {}
};
diff --git a/packages/glsp-playwright/package.json b/packages/glsp-playwright/package.json
index 0c33792..8b42bf5 100644
--- a/packages/glsp-playwright/package.json
+++ b/packages/glsp-playwright/package.json
@@ -45,14 +45,16 @@
"uuid": "^9.0.0"
},
"devDependencies": {
+ "@playwright/test": "^1.40.1",
+ "@theia/playwright": "~1.45.0",
"@types/uuid": "^9.0.0",
"@vscode/test-electron": "^2.3.2",
"concurrently": "^7.6.0",
"tsc-alias": "^1.8.2"
},
"peerDependencies": {
- "@playwright/test": "^1.32.1",
- "@theia/playwright": "^1.39.0"
+ "@playwright/test": "^1.40.1",
+ "@theia/playwright": "~1.45.0"
},
"publishConfig": {
"access": "public"
diff --git a/packages/glsp-playwright/src/extension/flows/index.ts b/packages/glsp-playwright/src/extension/flows/index.ts
index 38976d5..d7ab8ee 100644
--- a/packages/glsp-playwright/src/extension/flows/index.ts
+++ b/packages/glsp-playwright/src/extension/flows/index.ts
@@ -18,3 +18,4 @@ export * from './delete.flow';
export * from './drag.flow';
export * from './hover.flow';
export * from './rename.flow';
+export * from './select.flow';
diff --git a/packages/glsp-playwright/src/extension/flows/select.flow.ts b/packages/glsp-playwright/src/extension/flows/select.flow.ts
new file mode 100644
index 0000000..91266a7
--- /dev/null
+++ b/packages/glsp-playwright/src/extension/flows/select.flow.ts
@@ -0,0 +1,44 @@
+/********************************************************************************
+ * Copyright (c) 2024 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 { PModelElement } from '../../glsp';
+import type { ConstructorA } from '../../types';
+import type { Flow } from '../types';
+import { Clickable } from './click.flow';
+
+export interface SelectableOptions {
+ modifiers?: Array<'Alt' | 'Control' | 'Meta' | 'Shift'>;
+}
+
+export interface Selectable {
+ select(options?: SelectableOptions): Promise;
+}
+
+export namespace Selectable {
+ export const CSS = 'selected';
+}
+
+export function useSelectableFlow>(Base: TBase): Flow {
+ abstract class Mixin extends Base implements Selectable {
+ async select(options?: SelectableOptions): Promise {
+ await this.click(options);
+ return this.locate()
+ .and(this.page.locator(`.${Selectable.CSS}`))
+ .waitFor();
+ }
+ }
+
+ return Mixin;
+}
diff --git a/packages/glsp-playwright/src/glsp/app/app.po.ts b/packages/glsp-playwright/src/glsp/app/app.po.ts
index cd55bd1..45ae99a 100644
--- a/packages/glsp-playwright/src/glsp/app/app.po.ts
+++ b/packages/glsp-playwright/src/glsp/app/app.po.ts
@@ -16,7 +16,6 @@
import type { Locator, Page } from 'playwright-core';
import type { Integration } from '~/integration';
import { GLSPLocator } from '~/remote/locator';
-import type { ConstructorT } from '~/types';
import { GLSPGlobalCommandPalette } from '../features/command-palette';
import { GLSPLabelEditor } from '../features/label-editor/label-editor.po';
import { GLSPPopup } from '../features/popup/popup.po';
@@ -60,7 +59,7 @@ export type GLSPAppOptions = GLSPPageOptions | GLSPIntegrationOptions;
* Integration mode
* ```ts
* test.beforeEach(async ({ integration }) => {
- * app = await GLSPApp.loadApp(WorkflowApp, {
+ * app = await new GLSPApp({
* type: 'integration',
* integration
* });
@@ -71,7 +70,7 @@ export type GLSPAppOptions = GLSPPageOptions | GLSPIntegrationOptions;
* Page mode
* ```ts
* test.beforeEach(async ({ page }) => {
- * app = await GLSPApp.loadApp(WorkflowApp, {
+ * app = await new GLSPApp({
* type: 'page',
* page
* });
@@ -80,28 +79,30 @@ export type GLSPAppOptions = GLSPPageOptions | GLSPIntegrationOptions;
* ```
*/
export class GLSPApp {
- readonly rootLocator;
- readonly locator;
- readonly integration;
- readonly page;
-
- readonly graph;
- readonly labelEditor;
- readonly toolPalette;
- readonly popup;
- readonly globalCommandPalette;
-
- static async load(options: GLSPAppOptions): Promise {
- return this.loadApp(GLSPApp, options);
- }
+ readonly sprottySelector = 'div.sprotty';
- static async loadApp(appConstructor: ConstructorT, options: GLSPAppOptions): Promise {
- const app = new appConstructor(options);
- return app;
- }
+ rootLocator: GLSPLocator;
+ locator: GLSPLocator;
+ integration?: Integration;
+ page: Page;
+
+ graph;
+ labelEditor;
+ toolPalette;
+ popup;
+ globalCommandPalette;
constructor(public readonly options: GLSPAppOptions) {
- const sprottySelector = 'div.sprotty';
+ this.initialize(options);
+
+ this.graph = this.createGraph();
+ this.labelEditor = this.createLabelEditor();
+ this.toolPalette = this.createToolPalette();
+ this.popup = this.createPopup();
+ this.globalCommandPalette = this.createGlobalCommandPalette();
+ }
+
+ protected initialize(options: GLSPAppOptions): void {
let locate: (selector: string) => Locator;
if (options.type === 'page') {
@@ -119,13 +120,7 @@ export class GLSPApp {
options.rootSelector === undefined ? locate('body') : locate(options.rootSelector).locator('body'),
this
);
- this.locator = this.rootLocator.child(sprottySelector);
-
- this.graph = this.createGraph();
- this.labelEditor = this.createLabelEditor();
- this.toolPalette = this.createToolPalette();
- this.popup = this.createPopup();
- this.globalCommandPalette = this.createGlobalCommandPalette();
+ this.locator = this.rootLocator.child(this.sprottySelector);
}
protected createGraph(): GLSPGraph {
diff --git a/packages/glsp-playwright/src/glsp/graph/elements/element.ts b/packages/glsp-playwright/src/glsp/graph/elements/element.ts
index 775b37b..c932be8 100644
--- a/packages/glsp-playwright/src/glsp/graph/elements/element.ts
+++ b/packages/glsp-playwright/src/glsp/graph/elements/element.ts
@@ -13,6 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
+import { Locator } from '@playwright/test';
import { extractMetaTree } from '~/debugger/extractor';
import { GLSPLocator, Locateable } from '~/remote';
import type { ConstructorT } from '~/types/constructor';
@@ -21,27 +22,53 @@ import { ModelElementMetadata, PMetadata } from '../decorators';
import { SVGMetadata } from '../svg-metadata-api';
export async function assertEqualType(element: PModelElement): Promise {
- if ((await element.locate().count()) !== 1) {
+ const located = await element.locate().all();
+ const count = located.length;
+
+ if (count !== 1) {
+ // Provide additional information
+ for (const locator of located) {
+ console.error('=========== ACCESS RETURNS ==========');
+ console.error(await locator.evaluate(elem => elem.outerHTML));
+ }
+
throw Error(
- `Assertion failed. Locator of ${typeof PModelElement} did not find single element in the DOM. It found ${await element.locate()
- .count} elements.`
+ `Assertion failed. Locator did not find single element in the DOM.
+ It found ${count} elements for type ${element._metadata.type}.
+ Executed query: ${element.locate()}`
);
}
- const typeAttr = await element.typeAttr();
- if (typeAttr !== element._metadata.type) {
+ if (!(await isEqualType(element))) {
throw Error(
- `Assertion failed. Expected type '${element._metadata.type}', but it was '${typeAttr}'. Id = ${await element.idAttr()}`
+ `Assertion failed. Expected type '${
+ element._metadata.type
+ }', but it was '${await element.typeAttr()}'. Id = ${await element.idAttr()}`
);
}
}
+export async function isEqualType(element: PModelElement): Promise {
+ const typeAttr = await element.typeAttr();
+ return element._metadata.type === '*' || typeAttr === element._metadata.type;
+}
+
+export async function isEqualLocatorType(locator: Locator, constructor: PModelElementConstructor): Promise {
+ const typeAttr = await locator.getAttribute(SVGMetadata.type);
+ const constructorType = PMetadata.getType(constructor);
+
+ return typeAttr !== null && (constructorType === '*' || typeAttr === constructorType);
+}
+
export interface PModelElementData {
locator: GLSPLocator;
}
export type PModelElementConstructor = ConstructorT;
+@ModelElementMetadata({
+ type: '*'
+})
export class PModelElement extends Locateable {
readonly graph;
readonly _metadata;
diff --git a/packages/glsp-playwright/src/glsp/graph/graph.po.ts b/packages/glsp-playwright/src/glsp/graph/graph.po.ts
index 95331e7..8938224 100644
--- a/packages/glsp-playwright/src/glsp/graph/graph.po.ts
+++ b/packages/glsp-playwright/src/glsp/graph/graph.po.ts
@@ -21,11 +21,11 @@ import { definedAttr } from '~/utils/ts.utils';
import { PMetadata } from './decorators';
import { assertEqualType, createTypedEdgeProxy, getPModelElementConstructorOfType } from './elements';
import type { PEdge, PEdgeConstructor } from './elements/edge';
-import type { PModelElement, PModelElementConstructor } from './elements/element';
+import { PModelElement, PModelElementConstructor, isEqualLocatorType } from './elements/element';
import type { PNode, PNodeConstructor } from './elements/node';
import type { EdgeConstructorOptions, EdgeSearchOptions, ElementQuery, TypedEdge } from './graph.type';
import { waitForElementChanges, waitForElementIncrease } from './graph.wait';
-import { SVGMetadataUtils } from './svg-metadata-api';
+import { SVGMetadata, SVGMetadataUtils } from './svg-metadata-api';
export interface GLSPGraphOptions {
locator: GLSPLocator;
@@ -84,19 +84,29 @@ export class GLSPGraph extends Locateable {
const elements: TElement[] = [];
for await (const childLocator of await locator.all()) {
- const id = await definedAttr(childLocator, 'id');
- elements.push(await this.getModelElementBySelector(`#${id}`, constructor));
+ if ((await childLocator.count()) > 0) {
+ const id = await childLocator.getAttribute('id');
+ if (id !== null && (await isEqualLocatorType(childLocator, constructor))) {
+ elements.push(await this.getModelElementBySelector(`#${id}`, constructor));
+ }
+ }
}
return elements;
}
+ async getAllModelElements(): Promise {
+ return this.getModelElementsBySelector(`[${SVGMetadata.type}]`, PModelElement);
+ }
+
async getNodeBySelector(selector: string, constructor: PNodeConstructor): Promise {
return this.getNodeByLocator(this.locator.child(selector).locate(), constructor);
}
async getNodeByLocator(locator: Locator, constructor: PNodeConstructor): Promise {
- const element = new constructor({ locator: this.locator.override(locator) });
+ const element = new constructor({
+ locator: this.locator.override(locator.and(this.locate().locator(SVGMetadataUtils.typeAttrOf(constructor))))
+ });
await assertEqualType(element);
return element;
}
@@ -113,8 +123,12 @@ export class GLSPGraph extends Locateable {
const elements: TElement[] = [];
for await (const childLocator of await locator.all()) {
- const id = await definedAttr(childLocator, 'id');
- elements.push(await this.getNodeBySelector(`#${id}`, constructor));
+ if ((await childLocator.count()) > 0) {
+ const id = await childLocator.getAttribute('id');
+ if (id !== null && (await isEqualLocatorType(childLocator, constructor))) {
+ elements.push(await this.getNodeBySelector(`#${id}`, constructor));
+ }
+ }
}
return elements;
@@ -124,9 +138,17 @@ export class GLSPGraph extends Locateable {
selector: string,
constructor: PEdgeConstructor,
options?: TOptions
+ ): Promise> {
+ return this.getEdgeByLocator(this.locator.child(selector).locate(), constructor, options);
+ }
+
+ async getEdgeByLocator(
+ locator: Locator,
+ constructor: PEdgeConstructor,
+ options?: TOptions
): Promise> {
const element = new constructor({
- locator: this.locator.child(selector)
+ locator: this.locator.override(locator.and(this.locate().locator(SVGMetadataUtils.typeAttrOf(constructor))))
});
await assertEqualType(element);
return createTypedEdgeProxy(element, options);
@@ -139,43 +161,45 @@ export class GLSPGraph extends Locateable {
const elements: TypedEdge[] = [];
for await (const locator of await this.locate().locator(SVGMetadataUtils.typeAttrOf(constructor)).all()) {
- const id = await definedAttr(locator, 'id');
- const element = await this.getEdgeBySelector(`#${id}`, constructor, options);
- const sourceChecks = [];
- const targetChecks = [];
-
- if (options?.sourceConstructor) {
- const sourceId = await element.sourceId();
- sourceChecks.push(
- (await this.locate()
- .locator(`[id$="${sourceId}"]${SVGMetadataUtils.typeAttrOf(options.sourceConstructor)}`)
- .count()) > 0
- );
- }
-
- if (options?.targetConstructor) {
- const targetId = await element.targetId();
- targetChecks.push(
- (await this.locate()
- .locator(`[id$="${targetId}"]${SVGMetadataUtils.typeAttrOf(options.targetConstructor)}`)
- .count()) > 0
- );
- }
-
- if (options?.sourceSelector) {
- const sourceId = await element.sourceId();
- const expectedId = await definedAttr(this.locate().locator(options.sourceSelector), 'id');
- sourceChecks.push(expectedId.includes(sourceId));
- }
-
- if (options?.targetSelector) {
- const targetId = await element.targetId();
- const expectedId = await definedAttr(this.locate().locator(options.targetSelector), 'id');
- sourceChecks.push(expectedId.includes(targetId));
- }
-
- if (sourceChecks.every(c => c) && targetChecks.every(c => c)) {
- elements.push(element);
+ const id = await locator.getAttribute('id');
+ if (id !== null && (await isEqualLocatorType(locator, constructor))) {
+ const element = await this.getEdgeBySelector(`#${id}`, constructor, options);
+ const sourceChecks = [];
+ const targetChecks = [];
+
+ if (options?.sourceConstructor) {
+ const sourceId = await element.sourceId();
+ sourceChecks.push(
+ (await this.locate()
+ .locator(`[id$="${sourceId}"]${SVGMetadataUtils.typeAttrOf(options.sourceConstructor)}`)
+ .count()) > 0
+ );
+ }
+
+ if (options?.targetConstructor) {
+ const targetId = await element.targetId();
+ targetChecks.push(
+ (await this.locate()
+ .locator(`[id$="${targetId}"]${SVGMetadataUtils.typeAttrOf(options.targetConstructor)}`)
+ .count()) > 0
+ );
+ }
+
+ if (options?.sourceSelector) {
+ const sourceId = await element.sourceId();
+ const expectedId = await definedAttr(this.locate().locator(options.sourceSelector), 'id');
+ sourceChecks.push(expectedId.includes(sourceId));
+ }
+
+ if (options?.targetSelector) {
+ const targetId = await element.targetId();
+ const expectedId = await definedAttr(this.locate().locator(options.targetSelector), 'id');
+ sourceChecks.push(expectedId.includes(targetId));
+ }
+
+ if (sourceChecks.every(c => c) && targetChecks.every(c => c)) {
+ elements.push(element);
+ }
}
}
@@ -215,7 +239,19 @@ export class GLSPGraph extends Locateable {
const elementType = typeof type === 'string' ? type : PMetadata.getType(type);
const query: ElementQuery = {
elementType: elementType,
- all: () => this.getModelElementsOfType(getPModelElementConstructorOfType(elementType))
+ all: () => this.getModelElementsOfType(getPModelElementConstructorOfType(elementType)),
+ filter: async elements => {
+ const filtered: PModelElement[] = [];
+
+ for (const element of elements) {
+ const css = await element.classAttr();
+ if (css && !css.includes('ghost-element')) {
+ filtered.push(element);
+ }
+ }
+
+ return filtered;
+ }
};
const { before, after } = await waitForElementChanges(query, creator, b => waitForElementIncrease(this.locator, query, b.length));
diff --git a/packages/glsp-playwright/src/glsp/graph/graph.type.ts b/packages/glsp-playwright/src/glsp/graph/graph.type.ts
index 915e99f..1d9f640 100644
--- a/packages/glsp-playwright/src/glsp/graph/graph.type.ts
+++ b/packages/glsp-playwright/src/glsp/graph/graph.type.ts
@@ -42,4 +42,12 @@ export type TypedEdge {
elementType: string;
all: () => Promise;
+ filter?: (elements: TElement[]) => Promise;
+}
+
+export namespace ElementQuery {
+ export async function exec(query: ElementQuery): Promise {
+ const elements = await query.all();
+ return query.filter?.(elements) ?? elements;
+ }
}
diff --git a/packages/glsp-playwright/src/glsp/graph/graph.wait.ts b/packages/glsp-playwright/src/glsp/graph/graph.wait.ts
index 3fa7c44..e66c6bb 100644
--- a/packages/glsp-playwright/src/glsp/graph/graph.wait.ts
+++ b/packages/glsp-playwright/src/glsp/graph/graph.wait.ts
@@ -16,7 +16,7 @@
import { waitForFunction } from '~/integration/wait.fixes';
import type { GLSPLocator } from '~/remote/locator';
import type { PModelElement } from './elements/element';
-import type { ElementQuery } from './graph.type';
+import { ElementQuery } from './graph.type';
import { SVGMetadataUtils } from './svg-metadata-api';
/**
@@ -35,12 +35,12 @@ export async function waitForElementChanges(
before: TElement[];
after: TElement[];
}> {
- const before = await query.all();
+ const before = await ElementQuery.exec(query);
await operation();
await waitForOperation(before);
- const after = await query.all();
+ const after = await ElementQuery.exec(query);
return {
before,
diff --git a/packages/glsp-playwright/src/integration/integration.base.ts b/packages/glsp-playwright/src/integration/integration.base.ts
index f41c10d..efcd632 100644
--- a/packages/glsp-playwright/src/integration/integration.base.ts
+++ b/packages/glsp-playwright/src/integration/integration.base.ts
@@ -15,7 +15,7 @@
********************************************************************************/
import type { Locator, Page } from '@playwright/test';
import { SVGMetadataUtils } from '../glsp/index';
-import type { IntegrationType } from './integration.type';
+import type { IntegrationArgs, IntegrationType } from './integration.type';
/**
* Base class for all integrations. It provides lifecycle methods and
@@ -23,7 +23,11 @@ import type { IntegrationType } from './integration.type';
*/
export abstract class Integration {
abstract readonly page: Page;
- abstract readonly type: IntegrationType;
+
+ constructor(
+ protected readonly args: IntegrationArgs,
+ readonly type: IntegrationType
+ ) {}
/**
* Prefixes the provided selector and returns the new {@link Locator}.
diff --git a/packages/glsp-playwright/src/integration/integration.type.ts b/packages/glsp-playwright/src/integration/integration.type.ts
index cee3b5d..75cd6ba 100644
--- a/packages/glsp-playwright/src/integration/integration.type.ts
+++ b/packages/glsp-playwright/src/integration/integration.type.ts
@@ -13,6 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
+import type { Browser, Page } from '@playwright/test';
import type { PageIntegrationOptions } from './page';
import type { StandaloneIntegrationOptions } from './standalone';
import type { TheiaIntegrationOptions } from './theia';
@@ -25,3 +26,9 @@ export interface BaseIntegrationOptions {
}
export type IntegrationOptions = PageIntegrationOptions | StandaloneIntegrationOptions | TheiaIntegrationOptions | VSCodeIntegrationOptions;
+
+export interface IntegrationArgs {
+ page: Page;
+ playwright: typeof import('playwright-core');
+ browser: Browser;
+}
diff --git a/packages/glsp-playwright/src/integration/page/page.integration.ts b/packages/glsp-playwright/src/integration/page/page.integration.ts
index f906355..7decf66 100644
--- a/packages/glsp-playwright/src/integration/page/page.integration.ts
+++ b/packages/glsp-playwright/src/integration/page/page.integration.ts
@@ -13,20 +13,22 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import type { Page } from '@playwright/test';
import { SVGMetadataUtils } from '~/glsp';
import { Integration } from '../integration.base';
-import type { IntegrationType } from '../integration.type';
+import type { IntegrationArgs } from '../integration.type';
import type { PageIntegrationOptions } from './page.options';
/**
* The {@link PageIntegration} provides an unchanged experience of Playwright.
*/
export class PageIntegration extends Integration {
- readonly type: IntegrationType = 'Page';
+ override page = this.args.page;
- constructor(public readonly page: Page, protected readonly options?: PageIntegrationOptions) {
- super();
+ constructor(
+ args: IntegrationArgs,
+ protected readonly options?: PageIntegrationOptions
+ ) {
+ super(args, 'Page');
}
/**
diff --git a/packages/glsp-playwright/src/integration/standalone/standalone.integration.ts b/packages/glsp-playwright/src/integration/standalone/standalone.integration.ts
index c31bebf..10dc4cf 100644
--- a/packages/glsp-playwright/src/integration/standalone/standalone.integration.ts
+++ b/packages/glsp-playwright/src/integration/standalone/standalone.integration.ts
@@ -13,10 +13,9 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import type { Page } from '@playwright/test';
import { SVGMetadataUtils } from '~/glsp';
import { Integration } from '../integration.base';
-import type { IntegrationType } from '../integration.type';
+import type { IntegrationArgs } from '../integration.type';
import type { StandaloneIntegrationOptions } from './standalone.options';
/**
@@ -24,10 +23,13 @@ import type { StandaloneIntegrationOptions } from './standalone.options';
* with the standalone version of the GLSP-Client.
*/
export class StandaloneIntegration extends Integration {
- readonly type: IntegrationType = 'Standalone';
+ override page = this.args.page;
- constructor(public readonly page: Page, protected readonly options: StandaloneIntegrationOptions) {
- super();
+ constructor(
+ args: IntegrationArgs,
+ protected readonly options: StandaloneIntegrationOptions
+ ) {
+ super(args, 'Standalone');
}
/**
diff --git a/packages/glsp-playwright/src/integration/theia/po/theia-glsp-app.po.ts b/packages/glsp-playwright/src/integration/theia/po/theia-glsp-app.po.ts
index a47fd56..a69b5f1 100644
--- a/packages/glsp-playwright/src/integration/theia/po/theia-glsp-app.po.ts
+++ b/packages/glsp-playwright/src/integration/theia/po/theia-glsp-app.po.ts
@@ -14,7 +14,7 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import { TheiaApp } from '@theia/playwright';
+import { TheiaApp, TheiaEditor } from '@theia/playwright';
import type { TheiaIntegrationOptions } from '../theia.options';
/**
@@ -30,4 +30,13 @@ export class TheiaGLSPApp extends TheiaApp {
initialize(options: TheiaIntegrationOptions): void {
this._options = options;
}
+
+ override openEditor(
+ filePath: string,
+ editorFactory: new (editorFilePath: string, app: TheiaGLSPApp) => T,
+ editorName?: string | undefined,
+ expectFileNodes?: boolean | undefined
+ ): Promise {
+ return super.openEditor(filePath, editorFactory as new (f: string, a: TheiaApp) => T, editorName, expectFileNodes);
+ }
}
diff --git a/packages/glsp-playwright/src/integration/theia/theia.integration.ts b/packages/glsp-playwright/src/integration/theia/theia.integration.ts
index 1ce428c..67e6ebc 100644
--- a/packages/glsp-playwright/src/integration/theia/theia.integration.ts
+++ b/packages/glsp-playwright/src/integration/theia/theia.integration.ts
@@ -14,11 +14,11 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
-import type { Page } from '@playwright/test';
-import { TheiaApp, TheiaWorkspace } from '@theia/playwright';
+import { Page } from '@playwright/test';
+import { TheiaAppLoader, TheiaWorkspace } from '@theia/playwright';
import { SVGMetadataUtils } from '~/glsp';
import { Integration } from '../integration.base';
-import type { IntegrationType } from '../integration.type';
+import type { IntegrationArgs } from '../integration.type';
import { TheiaGLSPApp } from './po/theia-glsp-app.po';
import { TheiaGLSPEditor } from './po/theia-glsp-editor.po';
import { TheiaIntegrationOptions } from './theia.options';
@@ -28,24 +28,28 @@ import { TheiaIntegrationOptions } from './theia.options';
* with the Theia version of the GLSP-Client.
*/
export class TheiaIntegration extends Integration {
- readonly type: IntegrationType = 'Theia';
+ protected theiaApp: TheiaGLSPApp;
- constructor(public readonly page: Page, protected readonly options: TheiaIntegrationOptions) {
- super();
+ override get page(): Page {
+ return this.theiaApp.page;
+ }
+
+ constructor(
+ args: IntegrationArgs,
+ protected readonly options: TheiaIntegrationOptions
+ ) {
+ super(args, 'Theia');
}
protected override async launch(): Promise {
- await this.page.goto(this.options.url);
+ const ws = new TheiaWorkspace(this.options.workspace ? [this.options.workspace] : undefined);
+ this.theiaApp = await TheiaAppLoader.load(this.args, ws, TheiaGLSPApp as any);
+ this.theiaApp.initialize(this.options);
}
protected override async afterLaunch(): Promise {
- const ws = this.options.workspace ? new TheiaWorkspace([this.options.workspace]) : undefined;
-
- const theiaApp = await TheiaApp.loadApp(this.page as any, TheiaGLSPApp, ws);
- theiaApp.initialize(this.options);
-
if (this.options.file) {
- await theiaApp.openEditor(this.options.file, TheiaGLSPEditor as any);
+ await this.theiaApp.openEditor(this.options.file, TheiaGLSPEditor);
await this.assertMetadataAPI();
await this.page.waitForSelector(`${SVGMetadataUtils.typeAttrOf('graph')} svg.sprotty-graph > g`);
}
diff --git a/packages/glsp-playwright/src/integration/vscode/vscode.integration.ts b/packages/glsp-playwright/src/integration/vscode/vscode.integration.ts
index e88b634..d3b9fe4 100644
--- a/packages/glsp-playwright/src/integration/vscode/vscode.integration.ts
+++ b/packages/glsp-playwright/src/integration/vscode/vscode.integration.ts
@@ -22,7 +22,7 @@ import * as platformPath from 'path';
import { v4 as uuidv4 } from 'uuid';
import { SVGMetadataUtils } from '~/glsp';
import { Integration } from '../integration.base';
-import type { IntegrationType } from '../integration.type';
+import type { IntegrationArgs } from '../integration.type';
import { VSCodeWorkbenchActivitybar } from './po/workbench-activitybar.po';
import type { VSCodeIntegrationOptions } from './vscode.options';
import { VSCodeStorage } from './vscode.storage';
@@ -55,15 +55,18 @@ interface RunPaths {
* and the configuration will be saved in the temp folder.
*/
export class VSCodeIntegration extends Integration {
- readonly type: IntegrationType = 'VSCode';
+ protected _page: Page;
workbenchActivitybar: VSCodeWorkbenchActivitybar;
protected runConfig: VSCodeRunConfig;
protected electronApp: ElectronApplication;
protected storage: VSCodeStorage.Storage;
- constructor(protected readonly options: VSCodeIntegrationOptions) {
- super();
+ constructor(
+ args: IntegrationArgs,
+ protected readonly options: VSCodeIntegrationOptions
+ ) {
+ super(args, 'VSCode');
}
override async initialize(): Promise {
@@ -76,8 +79,8 @@ export class VSCodeIntegration extends Integration {
}
}
- get page(): Page {
- return this.electronApp.windows()[0];
+ override get page(): Page {
+ return this._page;
}
override prefixRootSelector(selector: string): Locator {
@@ -112,25 +115,13 @@ export class VSCodeIntegration extends Integration {
this.options.workspace
]
});
-
- this.electronApp.on('window', async page => {
- page.on('pageerror', error => {
- console.error(error);
- });
-
- page.on('console', msg => {
- if (this.options.isConsoleLogEnabled) {
- console.log(msg.text());
- }
- });
- });
}
protected override async afterLaunch(): Promise {
- const ePage = await this.electronApp.firstWindow();
- await ePage.waitForLoadState('domcontentloaded');
+ this._page = await this.electronApp.firstWindow();
+ await this.page.waitForLoadState('domcontentloaded');
- this.workbenchActivitybar = new VSCodeWorkbenchActivitybar(ePage);
+ this.workbenchActivitybar = new VSCodeWorkbenchActivitybar(this.page);
await this.waitForReady();
diff --git a/packages/glsp-playwright/src/integration/vscode/vscode.setup.ts b/packages/glsp-playwright/src/integration/vscode/vscode.setup.ts
index d6e8199..3ca628f 100644
--- a/packages/glsp-playwright/src/integration/vscode/vscode.setup.ts
+++ b/packages/glsp-playwright/src/integration/vscode/vscode.setup.ts
@@ -85,6 +85,7 @@ export class VSCodeSetup {
const extensionArg = defaultArgs[0].split(/=(.*)/s);
const extensionDir = extensionArg[1];
+ let status: number | undefined;
try {
const extensionsFile = `${extensionDir}/extensions.json`;
@@ -94,8 +95,8 @@ export class VSCodeSetup {
const entry = installedExtensions.find(e => e.identifier.id === this.integrationOptions.vsixId);
if (entry === undefined) {
- this.installExtension(cli, extensionArg, this.integrationOptions.vsixPath);
- this.log('[Extension] Extension installed');
+ const spawn = this.installExtension(cli, extensionArg, this.integrationOptions.vsixPath);
+ status = spawn.status ?? -1;
} else {
const stat = await fs.stat(this.integrationOptions.vsixPath);
if (entry.metadata.installedTimestamp < stat.birthtimeMs) {
@@ -105,8 +106,8 @@ export class VSCodeSetup {
new Date(stat.birthtimeMs).toISOString()
);
this.deleteExtension(cli, extensionArg, this.integrationOptions.vsixId);
- this.installExtension(cli, extensionArg, this.integrationOptions.vsixPath);
- this.log('[Extension] Extension installed');
+ const spawn = this.installExtension(cli, extensionArg, this.integrationOptions.vsixPath);
+ status = spawn.status ?? -1;
} else {
this.log('[Extension] Extension already installed. Skipping install');
}
@@ -114,8 +115,16 @@ export class VSCodeSetup {
} catch (ex) {
this.log('[Extension] Proceed with clean install.');
this.deleteExtension(cli, extensionArg, this.integrationOptions.vsixId);
- this.installExtension(cli, extensionArg, this.integrationOptions.vsixPath);
- this.log('[Extension] Extension installed');
+ const spawn = this.installExtension(cli, extensionArg, this.integrationOptions.vsixPath);
+ status = spawn.status ?? -1;
+ }
+
+ if (status) {
+ if (status === 0) {
+ this.log('[Extension] Extension installed');
+ } else {
+ throw new Error('[Extension] Extension install failed - Check logs');
+ }
}
}
@@ -125,17 +134,17 @@ export class VSCodeSetup {
}
}
- protected deleteExtension(cli: string, args: string[], vsixId: string): void {
+ protected deleteExtension(cli: string, args: string[], vsixId: string): cp.SpawnSyncReturns {
this.log('[Extension] Delete:', vsixId);
- cp.spawnSync(cli, [...args, '--uninstall-extension', vsixId], {
+ return cp.spawnSync(cli, [...args, '--uninstall-extension', vsixId], {
encoding: 'utf-8',
stdio: 'inherit'
});
}
- protected installExtension(cli: string, args: string[], vsixPath: string): void {
+ protected installExtension(cli: string, args: string[], vsixPath: string): cp.SpawnSyncReturns {
this.log('[Extension] Install:', vsixPath);
- cp.spawnSync(cli, [...args, '--install-extension', vsixPath], {
+ return cp.spawnSync(cli, [...args, '--install-extension', vsixPath], {
encoding: 'utf-8',
stdio: 'inherit'
});
diff --git a/packages/glsp-playwright/src/test.ts b/packages/glsp-playwright/src/test.ts
index 562ad3e..50b7c41 100644
--- a/packages/glsp-playwright/src/test.ts
+++ b/packages/glsp-playwright/src/test.ts
@@ -16,6 +16,7 @@
import { test as base } from '@playwright/test';
import {
Integration,
+ IntegrationArgs,
IntegrationOptions,
IntegrationType,
PageIntegration,
@@ -47,7 +48,7 @@ export interface GLSPPlaywrightFixtures {
*
* ```ts
* test.beforeEach(async ({ integration }) => {
- * app = await GLSPApp.loadApp(WorkflowApp, {
+ * app = new GLSPApp({
* type: 'integration',
* integration
* });
@@ -69,22 +70,27 @@ export const test = base.extend(
}
},
- integration: async ({ page, integrationOptions }, use) => {
+ integration: async ({ playwright, browser, page, integrationOptions }, use) => {
+ const args: IntegrationArgs = {
+ playwright,
+ browser,
+ page
+ };
+
if (integrationOptions) {
let integration: Integration;
-
switch (integrationOptions.type) {
case 'Page':
- integration = new PageIntegration(page, integrationOptions);
+ integration = new PageIntegration(args, integrationOptions);
break;
case 'Standalone':
- integration = new StandaloneIntegration(page, integrationOptions);
+ integration = new StandaloneIntegration(args, integrationOptions);
break;
case 'Theia':
- integration = new TheiaIntegration(page, integrationOptions);
+ integration = new TheiaIntegration(args, integrationOptions);
break;
case 'VSCode': {
- integration = new VSCodeIntegration(integrationOptions);
+ integration = new VSCodeIntegration(args, integrationOptions);
break;
}
default: {
@@ -97,7 +103,7 @@ export const test = base.extend(
await integration.start();
await use(integration);
} else {
- const integration = new PageIntegration(page);
+ const integration = new PageIntegration(args);
await integration.initialize();
await integration.start();
await use(integration);
@@ -120,5 +126,9 @@ export function createParameterizedIntegrationData(options: {
};
}
+export function skipIntegration(integrationOptions?: IntegrationOptions, ...integration: IntegrationType[]): boolean {
+ return integrationOptions === undefined || integration.includes(integrationOptions.type);
+}
+
export { expect } from '@playwright/test';
export { test as setup };
diff --git a/packages/glsp-playwright/src/utils/ts.utils.ts b/packages/glsp-playwright/src/utils/ts.utils.ts
index bfd3540..e6669c3 100644
--- a/packages/glsp-playwright/src/utils/ts.utils.ts
+++ b/packages/glsp-playwright/src/utils/ts.utils.ts
@@ -45,15 +45,12 @@ export function defined(value: T | undefined | null): T {
return value;
}
-/**
- *
- * @param locator Locator of the element
- * @param attr
- * @returns
- */
export async function definedGLSPAttr(locator: GLSPLocator, attr: string): Promise {
const o = await locator.locate().getAttribute(attr);
if (!isDefined(o)) {
+ // Provide additional information
+ console.error('=========== Element ==========');
+ console.error(await locator.locate().evaluate(elem => elem.outerHTML));
throw Error(`Attribute ${attr} is not defined for the selector.`);
}
@@ -63,6 +60,9 @@ export async function definedGLSPAttr(locator: GLSPLocator, attr: string): Promi
export async function definedAttr(locator: Locator, attr: string): Promise {
const o = await locator.getAttribute(attr);
if (!isDefined(o)) {
+ // Provide additional information
+ console.error('=========== Element ==========');
+ console.error(await locator.evaluate(elem => elem.outerHTML));
throw Error(`Attribute ${attr} is not defined for the selector`);
}
diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json
index 10da575..bbc123f 100644
--- a/tsconfig.eslint.json
+++ b/tsconfig.eslint.json
@@ -9,6 +9,7 @@
"examples/*/src",
"examples/*/tests",
"examples/*/scripts",
- "examples/*/*.config.ts"
+ "examples/*/*.config.ts",
+ "examples/*/configs/*.ts"
]
}
diff --git a/yarn.lock b/yarn.lock
index ae63c92..062a4de 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -922,19 +922,12 @@
picocolors "^1.0.0"
tslib "^2.6.0"
-"@playwright/test@^1.32.1":
- version "1.41.2"
- resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.41.2.tgz#bd9db40177f8fd442e16e14e0389d23751cdfc54"
- integrity sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==
+"@playwright/test@^1.37.1", "@playwright/test@^1.40.1":
+ version "1.40.1"
+ resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.40.1.tgz#9e66322d97b1d74b9f8718bacab15080f24cde65"
+ integrity sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==
dependencies:
- playwright "1.41.2"
-
-"@playwright/test@^1.34.3":
- version "1.39.0"
- resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.39.0.tgz#d10ba8e38e44104499e25001945f07faa9fa91cd"
- integrity sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==
- dependencies:
- playwright "1.39.0"
+ playwright "1.40.1"
"@sigstore/bundle@^1.1.0":
version "1.1.0"
@@ -1005,12 +998,12 @@
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918"
integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==
-"@theia/playwright@1.39.0":
- version "1.39.0"
- resolved "https://registry.yarnpkg.com/@theia/playwright/-/playwright-1.39.0.tgz#e74da1ac05dafeda134f086ccc54f2cb70135625"
- integrity sha512-P6e5mfYINiaA3tcQ5o7Z1Yc9vZJ0eWPTnSnFrDAwkgzP2AaDiSbr2ZUeuiyFvQBoZNzfpxJ6csheYkA/LMd/OA==
+"@theia/playwright@~1.45.0":
+ version "1.45.1"
+ resolved "https://registry.yarnpkg.com/@theia/playwright/-/playwright-1.45.1.tgz#c1447ffd48bfc61dfaafbf9669bdc6f8d0feedec"
+ integrity sha512-Kta1ChkyHgsFppjSYvun6+NRayukHTjnIsX5SLpB8PwlWMo6ftz6oGP9XWgoeXNvg2ldoTjQGNNAjjr8Ku0YyA==
dependencies:
- "@playwright/test" "^1.32.1"
+ "@playwright/test" "^1.37.1"
fs-extra "^9.0.8"
"@tootallnate/once@1":
@@ -4603,35 +4596,6 @@ mute-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e"
integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==
-mvn-artifact-download@5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/mvn-artifact-download/-/mvn-artifact-download-5.1.0.tgz#332c89992196e307cd00768e0fcb29cdb30a814a"
- integrity sha512-8Cdikbcotum2TZDI6bn3DtYYKIyjFKTDbM9CCrSLWVkAa8fd5s5Di1ti30I0J3vFYx+a2ZCcnaqNSgfvxZFMxQ==
- dependencies:
- mvn-artifact-filename "^5.1.0"
- mvn-artifact-name-parser "^5.0.1"
- mvn-artifact-url "^5.1.0"
- node-fetch "^2.6.0"
-
-mvn-artifact-filename@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/mvn-artifact-filename/-/mvn-artifact-filename-5.1.0.tgz#722e548b2a517a2af2ff6982e2952cde91346f55"
- integrity sha512-HgChSCBgeTQhWw4ELf0SDIyE8eok2A328aPq6BkPbJc5Pv9HNoREwhw887LnNXpNKty+xaE84DR3IguW+ct5Zw==
-
-mvn-artifact-name-parser@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/mvn-artifact-name-parser/-/mvn-artifact-name-parser-5.0.1.tgz#e2467a4809a1cc4285e80b4344ff06daef3540a0"
- integrity sha512-CyWPiWAe3Ubnf9joDLg9cIYoGu/QZXg926qXtohXdx/B/ZHTG/hXwtKt/vmezRw68irmUxAYRYCQCuolmgNLAA==
-
-mvn-artifact-url@^5.1.0:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/mvn-artifact-url/-/mvn-artifact-url-5.1.1.tgz#3634c33e7f73d3482c2a6f25faf9f4790052603a"
- integrity sha512-vtVMlWaLcXkiKnc/YSE3azKwfxDCMdwoaHSb7Yj5lcX0yEsC+VwKI49V7+Rvl261BjNQrm2MzJ5NbmDxRaQJZw==
- dependencies:
- mvn-artifact-filename "^5.1.0"
- node-fetch "^2.6.0"
- xml2js "^0.4.23"
-
mylas@^2.1.9:
version "2.1.13"
resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.13.tgz#1e23b37d58fdcc76e15d8a5ed23f9ae9fc0cbdf4"
@@ -4680,7 +4644,7 @@ node-fetch@2.6.7:
dependencies:
whatwg-url "^5.0.0"
-node-fetch@^2.6.0, node-fetch@^2.6.11, node-fetch@^2.6.7:
+node-fetch@^2.6.11, node-fetch@^2.6.7:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
@@ -5373,31 +5337,17 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
-playwright-core@1.39.0:
- version "1.39.0"
- resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.39.0.tgz#efeaea754af4fb170d11845b8da30b2323287c63"
- integrity sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==
-
-playwright-core@1.41.2:
- version "1.41.2"
- resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.41.2.tgz#db22372c708926c697acc261f0ef8406606802d9"
- integrity sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==
-
-playwright@1.39.0:
- version "1.39.0"
- resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.39.0.tgz#184c81cd6478f8da28bcd9e60e94fcebf566e077"
- integrity sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==
- dependencies:
- playwright-core "1.39.0"
- optionalDependencies:
- fsevents "2.3.2"
+playwright-core@1.40.1:
+ version "1.40.1"
+ resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.40.1.tgz#442d15e86866a87d90d07af528e0afabe4c75c05"
+ integrity sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==
-playwright@1.41.2:
- version "1.41.2"
- resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.41.2.tgz#4e760b1c79f33d9129a8c65cc27953be6dd35042"
- integrity sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==
+playwright@1.40.1:
+ version "1.40.1"
+ resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.40.1.tgz#a11bf8dca15be5a194851dbbf3df235b9f53d7ae"
+ integrity sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==
dependencies:
- playwright-core "1.41.2"
+ playwright-core "1.40.1"
optionalDependencies:
fsevents "2.3.2"
@@ -5795,11 +5745,6 @@ safe-regex-test@^1.0.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-sax@>=0.6.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
- integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
-
"semver@2 || 3 || 4 || 5", semver@^5.6.0:
version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
@@ -6817,24 +6762,11 @@ write-pkg@4.0.0:
type-fest "^0.4.1"
write-json-file "^3.2.0"
-xml2js@^0.4.23:
- version "0.4.23"
- resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
- integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
- dependencies:
- sax ">=0.6.0"
- xmlbuilder "~11.0.0"
-
xml@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==
-xmlbuilder@~11.0.0:
- version "11.0.1"
- resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
- integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
-
xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"