diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..0058922 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,10 @@ +# We use the Node-16 on Debian Bullseye. +ARG VARIANT="16-bullseye" +FROM mcr.microsoft.com/devcontainers/typescript-node:0-${VARIANT} + +# Install OS packages needed for building Theia. +RUN apt-get update \ + && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends \ + libsecret-1-dev \ + libxkbfile-dev \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8dbfa27..5a465d8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,18 +1,55 @@ // Dev-Container for CrossModel. -// For format details, see https://aka.ms/devcontainer.json. { "name": "Node.js & TypeScript", - // https://github.com/devcontainers/templates/tree/main/src/typescript-node - "image": "mcr.microsoft.com/devcontainers/typescript-node:1-16-bullseye", + "build": { + "dockerfile": "Dockerfile" + }, "features": { "ghcr.io/devcontainers/features/python:1": { "version": "3.11.4" } }, - + "customizations": { + "vscode": { + // Install a list of extensions in the Dev Container so everything is reasdy to develop and test. + "extensions": [ + "langium.langium-vscode", + "esbenp.prettier-vscode", + "eamodio.gitlens", + "davidanson.vscode-markdownlint", + "ms-playwright.playwright", + "dbaeumer.vscode-eslint", + "orta.vscode-jest" + ] + } + }, // Allow port 3000 to be forwarded. "forwardPorts": [3000], - - // Install the needed OS libs for building. - "postCreateCommand": "sudo apt-get update && sudo apt-get -y install libsecret-1-dev libxkbfile-dev" + "portsAttributes": { + "3000": { + "label": "Theia Backend" + } + }, + // Install the playwright dependencies. + "postCreateCommand": "yarn && yarn playwright install --with-deps", + // When we want to be able to run playwright headed within the Dev Container, + // we need to setup the mounts and container environment variables like below. + "mounts": [ + { + "source": "/run/desktop/mnt/host/wslg/.X11-unix", + "target": "/tmp/.X11-unix", + "type": "bind" + }, + { + "source": "/run/desktop/mnt/host/wslg", + "target": "/mnt/wslg", + "type": "bind" + } + ], + "containerEnv": { + "DISPLAY": ":0", + "WAYLAND_DISPLAY": "wayland-0", + "XDG_RUNTIME_DIR": "/mnt/wslg/runtime-dir", + "PULSE_SERVER": "/mnt/wslg/PulseServer" + } } diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 38192e2..7d3f312 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -46,43 +46,61 @@ jobs: yarn build # Execute the tests. - - name: Test + - name: Run Unit Tests run: yarn test env: # The test result file name can be controlled using the following environment variable. JEST_JUNIT_OUTPUT_NAME: unit-test-results-${{ runner.os }}.xml - # Upload Test Results (The different files for the OSes will end up in the same artifact). - - name: Upload Test Results + # Upload Unit Test Results (The different files for the OSes will end up in the same artifact). + - name: Upload Unit Test Results if: always() uses: actions/upload-artifact@v3 with: name: unit-test-tesults # Include the unit-test-results folders (which is in the root of the workspace). path: unit-test-results + retention-days: 30 + + # Run PlayWright tests, only on Linux container. + - name: Install Playwright Browsers + if: runner.os == 'Linux' + run: yarn --cwd ./e2e-tests/ playwright:install + - name: Run Playwright tests + if: runner.os == 'Linux' + uses: coactions/setup-xvfb@v1 + with: + run: yarn ui-test + - name: Upload PlayWrite test report + if: always() && runner.os == 'Linux' + uses: actions/upload-artifact@v3 + with: + name: allure-results + path: e2e-tests/allure-results/ + retention-days: 30 # Run lint only on Linux (since it only makes sense to run it once, and linux is the fastest). - name: Lint - if: runner.os == 'Linux' + if: always() && runner.os == 'Linux' run: yarn lint - # Publish a test report using the test result files published in the previous step (executed per OS). - publish-test-report: - name: Publish Test Report + # Publish a test report using the unit test result files published in the previous step (which was executed per OS). + publish-unit-test-report: + name: Publish Unit Test Report needs: build-and-test runs-on: ubuntu-latest if: always() steps: # Download the test results artifacts. - - name: Download Test Results + - name: Download Unit Test Results uses: actions/download-artifact@v3 with: name: unit-test-tesults path: unit-test-tesults # Publish Test Results - - name: Publish Test Results + - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v2 with: - check_name: Test Results + check_name: Unit Test Results files: | unit-test-tesults/**/*.xml diff --git a/.github/workflows/cicd-main.yml b/.github/workflows/cicd-main.yml index 46fdcd9..c3f4945 100644 --- a/.github/workflows/cicd-main.yml +++ b/.github/workflows/cicd-main.yml @@ -8,3 +8,55 @@ on: jobs: build-and-test: uses: ./.github/workflows/build-and-test.yml + # Publish a test report using the playwright result files published in the previous step (execute in Linux only). + publish-playwright-test-report: + name: Publish PlayWright Test Report + needs: build-and-test + permissions: + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + # Deploy to the github-pages environment + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + if: always() + steps: + # Setup the GitHub Pages, if it doesn't exist yet. + - name: Setup Pages + uses: actions/configure-pages@v3 + # Download the test results artifacts. + - name: Download Test Results + uses: actions/download-artifact@v3 + with: + name: allure-results + path: allure-results + # Get the gh-pages history, so the next report can be generated with history. + - name: Get History + uses: actions/checkout@v4 + continue-on-error: true + with: + ref: gh-pages + path: gh-pages + # Generate the Allure Report + - name: Generate Allure Report + uses: simple-elf/allure-report-action@master + with: + # Where to find the allure results. + allure_results: allure-results + # Where to publish the history. + allure_history: allure-history + keep_reports: 100 + # Subfolder in the destination. + subfolder: allure + # Where to find the gh-pages history. + gh_pages: gh-pages + # Upload allure-history report to github-pages artifact. + - name: Upload Pages + uses: actions/upload-pages-artifact@v2 + with: + path: 'allure-history' + # Deploy the github-pages artifact to GitHub pages. + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.gitignore b/.gitignore index 001d71f..cdcb54a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,8 @@ bin .ignored_out dist *.port -**/unit-test-results +**/*test-results **/coverage +**/playwright-report +**/playwright/.cache +**/allure-results \ No newline at end of file diff --git a/README.md b/README.md index 6f89813..34ac674 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ _or:_ launch `Launch CrossModel Electron` configuration from VS code. ## Example Workspace -Under `examples/workspace` we provide an example workspace with some demo packages containing entities, relationships and system diagrams. +Under `examples/verdaccio-example/workspace` we provide an example workspace with some demo packages containing entities, relationships and system diagrams. Each package represents a dedicated system or library and may depend on other packages. Using a known package structure - npm in our case - we can re-use large parts of the package management to download dependencies that are not locally available from an external package registry. @@ -35,7 +35,7 @@ You can start verdaccio using The local npm registry will be available under `http://localhost:4873/` where we already provide four packages by default. -After opening the workspace, you can install the necessary dependencies in the example workspace by opening a terminal in `examples/workspace` and execute +After opening the workspace, you can install the necessary dependencies in the example workspace by opening a terminal in `examples/verdaccio-example/workspace` and execute npm install @@ -65,6 +65,10 @@ Any code changes will be automatically detected and the application will be re-c If you only made changes to the frontend or plugins, simply reloading the running application with `F5` is enough. If you also made changes to the backend, you can close and restart the appliaction without manual re-compilation. +### Developing in Dev Container + +You can work on CrossModel from within a Dev Container. The best way to do so is create a new Dev Container and cloning the repository in there. For instructions please consult [this](https://code.visualstudio.com/docs/devcontainers/containers#_quick-start-open-a-git-repository-or-github-pr-in-an-isolated-container-volume) page. + ## Packaging To package the application use diff --git a/e2e-tests/package.json b/e2e-tests/package.json new file mode 100644 index 0000000..006504f --- /dev/null +++ b/e2e-tests/package.json @@ -0,0 +1,25 @@ +{ + "name": "crossmodel-e2e-tests", + "version": "0.0.0", + "private": true, + "license": "AGPL-3.0-or-later", + "author": { + "name": "CrossBreeze", + "email": "devops@crossbreeze.nl" + }, + "scripts": { + "build": "tsc -b && npx playwright install chromium", + "clean": "rimraf lib tsconfig.tsbuildinfo", + "lint": "eslint -c ../.eslintrc.js --ext .ts ./src", + "playwright:install": "yarn playwright install --with-deps", + "prepare": "yarn clean && yarn build && yarn lint", + "test": "yarn playwright test" + }, + "dependencies": { + "@playwright/test": "^1.37.1", + "@theia/playwright": "1.43.1" + }, + "devDependencies": { + "allure-playwright": "^2.9.2" + } +} diff --git a/e2e-tests/playwright.config.ts b/e2e-tests/playwright.config.ts new file mode 100644 index 0000000..56489e2 --- /dev/null +++ b/e2e-tests/playwright.config.ts @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: './src/tests', + testMatch: ['**/*.spec.ts'], + workers: process.env.CI ? 1 : 2, + retries: process.env.CI ? 1 : 0, + // The number of times to repeat each test, useful for debugging flaky tests + repeatEach: 1, + // Timeout for each test in milliseconds: 60 seconds. + timeout: 60 * 1000, + use: { + baseURL: 'http://localhost:3000', + browserName: 'chromium', + screenshot: 'only-on-failure', + viewport: { width: 1920, height: 1080 } + }, + snapshotDir: './src/tests/snapshots', + expect: { + toMatchSnapshot: { threshold: 0.01 } + }, + preserveOutput: 'failures-only', + reporter: process.env.CI ? [['list'], ['allure-playwright'], ['github']] : [['list'], ['html']], + /* Run your local dev server before starting the tests */ + webServer: { + command: 'yarn --cwd ../../ start:browser', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI + } +}); diff --git a/e2e-tests/src/fixtures/crossmodel-fixture.ts b/e2e-tests/src/fixtures/crossmodel-fixture.ts new file mode 100644 index 0000000..32540ac --- /dev/null +++ b/e2e-tests/src/fixtures/crossmodel-fixture.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ +import { test, Page } from '@playwright/test'; +import { CrossModelApp } from '../page-objects/crossmodel-app'; +import { CrossModelWorkspace } from '../page-objects/crossmodel-workspace'; + +export let page: Page; +export let app: CrossModelApp; + +test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + const ws = new CrossModelWorkspace(['src/resources/sample-workspace']); + app = await CrossModelApp.load(page, ws); +}); + +export default test; diff --git a/e2e-tests/src/page-objects/crossmodel-app.ts b/e2e-tests/src/page-objects/crossmodel-app.ts new file mode 100644 index 0000000..1eae530 --- /dev/null +++ b/e2e-tests/src/page-objects/crossmodel-app.ts @@ -0,0 +1,6 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ +import { TheiaApp } from '@theia/playwright'; + +export class CrossModelApp extends TheiaApp {} diff --git a/e2e-tests/src/page-objects/crossmodel-explorer-view.ts b/e2e-tests/src/page-objects/crossmodel-explorer-view.ts new file mode 100644 index 0000000..12e16cf --- /dev/null +++ b/e2e-tests/src/page-objects/crossmodel-explorer-view.ts @@ -0,0 +1,14 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ +import { TheiaApp, TheiaExplorerView } from '@theia/playwright'; +import { TheiaTabBarToolbar } from './theia-tabbar-toolbar'; + +export class CrossModelExplorerView extends TheiaExplorerView { + public readonly tabBarToolbar: TheiaTabBarToolbar; + + constructor(app: TheiaApp) { + super(app); + this.tabBarToolbar = new TheiaTabBarToolbar(this); + } +} diff --git a/e2e-tests/src/page-objects/crossmodel-workspace.ts b/e2e-tests/src/page-objects/crossmodel-workspace.ts new file mode 100644 index 0000000..faaa115 --- /dev/null +++ b/e2e-tests/src/page-objects/crossmodel-workspace.ts @@ -0,0 +1,6 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ +import { TheiaWorkspace } from '@theia/playwright'; + +export class CrossModelWorkspace extends TheiaWorkspace {} diff --git a/e2e-tests/src/page-objects/theia-single-input-dialog.ts b/e2e-tests/src/page-objects/theia-single-input-dialog.ts new file mode 100644 index 0000000..bef27f2 --- /dev/null +++ b/e2e-tests/src/page-objects/theia-single-input-dialog.ts @@ -0,0 +1,20 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ +import { OSUtil, TheiaDialog, USER_KEY_TYPING_DELAY } from '@theia/playwright'; + +export class TheiaSingleInputDialog extends TheiaDialog { + async enterSingleInput(inputValue: string): Promise { + const inputField = await this.page.waitForSelector(`${this.blockSelector} .theia-input`); + await inputField.press(OSUtil.isMacOS ? 'Meta+a' : 'Control+a'); + await inputField.fill(inputValue); + await this.page.waitForTimeout(USER_KEY_TYPING_DELAY); + } + + async confirm(): Promise { + if (!(await this.validationResult())) { + throw new Error(`Unexpected validation error in TheiaSingleInputDialog: '${await this.getValidationText()}`); + } + await this.clickMainButton(); + } +} diff --git a/e2e-tests/src/page-objects/theia-tabbar-toolbar.ts b/e2e-tests/src/page-objects/theia-tabbar-toolbar.ts new file mode 100644 index 0000000..e93cbbb --- /dev/null +++ b/e2e-tests/src/page-objects/theia-tabbar-toolbar.ts @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ +import { TheiaToolbarItem, TheiaView } from '@theia/playwright'; +import { TheiaViewObject } from './theia-view-object'; + +export class TheiaTabBarToolbar extends TheiaViewObject { + constructor(view: TheiaView) { + super(view, '.p-TabBar-toolbar'); + } + + async toolBarItem(commandId: string): Promise { + const toolbarHandle = await this.objectElementHandle(); + if (!toolbarHandle) { + return undefined; + } + const item = await toolbarHandle.$(this.toolBarItemSelector(commandId)); + if (item) { + return new TheiaToolbarItem(this.app, item); + } + return undefined; + } + + protected toolBarItemSelector(toolbarItemId = ''): string { + return `div.item > div${toolbarItemId ? `[id="${toolbarItemId}"]` : ''}`; + } +} diff --git a/e2e-tests/src/page-objects/theia-view-object.ts b/e2e-tests/src/page-objects/theia-view-object.ts new file mode 100644 index 0000000..a091220 --- /dev/null +++ b/e2e-tests/src/page-objects/theia-view-object.ts @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ + +import { ElementHandle } from '@playwright/test'; +import { TheiaPageObject, TheiaView } from '@theia/playwright'; + +export class TheiaViewObject extends TheiaPageObject { + protected selector: string; + + constructor(public view: TheiaView, protected relativeSelector: string) { + super(view.app); + this.selector = this.view.viewSelector + ' ' + relativeSelector; + } + + protected async objectElementHandle(): Promise | null> { + return this.page.$(this.selector); + } + + async waitForVisible(): Promise { + await this.page.waitForSelector(this.selector, { state: 'visible' }); + } + + async isVisible(): Promise { + const viewObject = await this.objectElementHandle(); + return !!viewObject && viewObject.isVisible(); + } +} diff --git a/e2e-tests/src/resources/sample-workspace/example-entity.cm b/e2e-tests/src/resources/sample-workspace/example-entity.cm new file mode 100644 index 0000000..48e512a --- /dev/null +++ b/e2e-tests/src/resources/sample-workspace/example-entity.cm @@ -0,0 +1,14 @@ +entity: + id: "ExampleEntity" + name: "Example Entity" + description: "An example entity" + attributes: + - id: "Id" + name: "Id" + datatype: "Integer" + - id: "FirstAttribute" + name: "First Attribute" + datatype: "Varchar" + - id: "SecondAttribute" + name: "Second Attribute" + datatype: "Varchar" \ No newline at end of file diff --git a/e2e-tests/src/tests/crossmodel-app.spec.ts b/e2e-tests/src/tests/crossmodel-app.spec.ts new file mode 100644 index 0000000..fdd0eaf --- /dev/null +++ b/e2e-tests/src/tests/crossmodel-app.spec.ts @@ -0,0 +1,11 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ +import { expect } from '@playwright/test'; +import test, { app } from '../fixtures/crossmodel-fixture'; + +test.describe('CrossModel App', () => { + test('main content panel visible', async () => { + expect(await app.isMainContentPanelVisible()).toBe(true); + }); +}); diff --git a/e2e-tests/src/tests/crossmodel-explorer-view.spec.ts b/e2e-tests/src/tests/crossmodel-explorer-view.spec.ts new file mode 100644 index 0000000..ccf3be4 --- /dev/null +++ b/e2e-tests/src/tests/crossmodel-explorer-view.spec.ts @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ +import { expect } from '@playwright/test'; +import { CrossModelExplorerView } from '../page-objects/crossmodel-explorer-view'; +import test, { app } from '../fixtures/crossmodel-fixture'; + +let explorer: CrossModelExplorerView; + +test.describe('CrossModel Explorer View', () => { + test.beforeAll(async ({ browser }) => { + explorer = await app.openView(CrossModelExplorerView); + await explorer.waitForVisibleFileNodes(); + }); + + test('code and form editor options available in the context menu on an entity', async () => { + const file = await explorer.getFileStatNodeByLabel('example-entity.cm'); + expect(file).toBeDefined(); + expect(await file.label()).toBe('example-entity.cm'); + const menu = await file.openContextMenu(); + expect(await menu.isOpen()).toBe(true); + // Expect the Code and Form editor to be in the Open With menu option. + expect(await menu.menuItemByNamePath('Open With', 'Code Editor')).toBeDefined(); + expect(await menu.menuItemByNamePath('Open With', 'Form Editor')).toBeDefined(); + }); +}); diff --git a/e2e-tests/src/tests/crossmodel-tabbar-toolbar.spec.ts b/e2e-tests/src/tests/crossmodel-tabbar-toolbar.spec.ts new file mode 100644 index 0000000..af6c2de --- /dev/null +++ b/e2e-tests/src/tests/crossmodel-tabbar-toolbar.spec.ts @@ -0,0 +1,114 @@ +/******************************************************************************** + * Copyright (c) 2023 CrossBreeze. + ********************************************************************************/ +import { expect } from '@playwright/test'; +import { CrossModelExplorerView } from '../page-objects/crossmodel-explorer-view'; +import { TheiaSingleInputDialog } from '../page-objects/theia-single-input-dialog'; +import { TheiaTabBarToolbar } from '../page-objects/theia-tabbar-toolbar'; +import test, { app } from '../fixtures/crossmodel-fixture'; + +let explorer: CrossModelExplorerView; +let tabBarToolbar: TheiaTabBarToolbar; + +test.describe('CrossModel TabBar Toolbar', () => { + test.beforeAll(async ({ browser }) => { + explorer = await app.openView(CrossModelExplorerView); + await explorer.waitForVisibleFileNodes(); + tabBarToolbar = explorer.tabBarToolbar; + }); + + test.beforeEach(async () => { + await explorer.focus(); + await tabBarToolbar.waitForVisible(); + }); + + test('create new entity from tabbar toolbar', async () => { + // Get the new-entity toolbar item. + const tabBarToolbarNewEntity = await tabBarToolbar.toolBarItem('crossbreeze.new.entity.toolbar'); + expect(tabBarToolbarNewEntity).toBeDefined(); + if (tabBarToolbarNewEntity) { + // expect(await tabBarToolbarNewEntity.isEnabled()).toBe(true); + // Click on the new-entity toolbar item. + await tabBarToolbarNewEntity.trigger(); + + const newEntityDialog = new TheiaSingleInputDialog(app); + // Wait for the New Entity dailog to popup. + newEntityDialog.waitForVisible(); + // Check the title of the dialog. + expect(await newEntityDialog.title()).toBe('New Entity...'); + // Set the name for the new entity. + await newEntityDialog.enterSingleInput('entity-created-from-tabbar-toolbar'); + // Wait until we can click the main button. + await newEntityDialog.waitUntilMainButtonIsEnabled(); + // Confirm the dialog. + await newEntityDialog.confirm(); + // Wait until the dialog is closed. + await newEntityDialog.waitForClosed(); + + explorer = await app.openView(CrossModelExplorerView); + const file = await explorer.getFileStatNodeByLabel('entity-created-from-tabbar-toolbar.entity.cm'); + expect(file).toBeDefined(); + expect(await file.label()).toBe('entity-created-from-tabbar-toolbar.entity.cm'); + } + }); + + test('create new relationship from tabbar toolbar', async () => { + // Get the new-entity toolbar item. + const tabBarToolbarNewEntity = await tabBarToolbar.toolBarItem('crossbreeze.new.relationship.toolbar'); + expect(tabBarToolbarNewEntity).toBeDefined(); + if (tabBarToolbarNewEntity) { + // expect(await tabBarToolbarNewEntity.isEnabled()).toBe(true); + // Click on the new-entity toolbar item. + await tabBarToolbarNewEntity.trigger(); + + const newRelationshipDialog = new TheiaSingleInputDialog(app); + // Wait for the New Entity dailog to popup. + newRelationshipDialog.waitForVisible(); + // Check the title of the dialog. + expect(await newRelationshipDialog.title()).toBe('New Relationship...'); + // Set the name for the new entity. + await newRelationshipDialog.enterSingleInput('relationship-created-from-tabbar-toolbar'); + // Wait until we can click the main button. + await newRelationshipDialog.waitUntilMainButtonIsEnabled(); + // Confirm the dialog. + await newRelationshipDialog.confirm(); + // Wait until the dialog is closed. + await newRelationshipDialog.waitForClosed(); + + explorer = await app.openView(CrossModelExplorerView); + const file = await explorer.getFileStatNodeByLabel('relationship-created-from-tabbar-toolbar.relationship.cm'); + expect(file).toBeDefined(); + expect(await file.label()).toBe('relationship-created-from-tabbar-toolbar.relationship.cm'); + } + }); + + test('create new diagram from tabbar toolbar', async () => { + // Get the new-entity toolbar item. + const tabBarToolbarNewEntity = await tabBarToolbar.toolBarItem('crossbreeze.new.diagram.toolbar'); + expect(tabBarToolbarNewEntity).toBeDefined(); + if (tabBarToolbarNewEntity) { + // expect(await tabBarToolbarNewEntity.isEnabled()).toBe(true); + // Click on the new-entity toolbar item. + await tabBarToolbarNewEntity.trigger(); + + const newDiagramDialog = new TheiaSingleInputDialog(app); + // Wait for the New Entity dailog to popup. + newDiagramDialog.waitForVisible(); + // Check the title of the dialog. + expect(await newDiagramDialog.title()).toBe('New Diagram...'); + // Set the name for the new entity. + await newDiagramDialog.enterSingleInput('diagram-created-from-tabbar-toolbar'); + // Wait until we can click the main button. + await newDiagramDialog.waitUntilMainButtonIsEnabled(); + // Confirm the dialog. + await newDiagramDialog.confirm(); + // Wait until the dialog is closed. + await newDiagramDialog.waitForClosed(); + + explorer = await app.openView(CrossModelExplorerView); + const file = await explorer.getFileStatNodeByLabel('diagram-created-from-tabbar-toolbar.diagram.cm'); + expect(file).toBeDefined(); + expect(await file.label()).toBe('diagram-created-from-tabbar-toolbar.diagram.cm'); + } + }); +}); diff --git a/e2e-tests/tsconfig.json b/e2e-tests/tsconfig.json new file mode 100644 index 0000000..b44b975 --- /dev/null +++ b/e2e-tests/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../configs/base.tsconfig.json", + "compilerOptions": { + "composite": true, + "baseUrl": ".", + "rootDir": ".", + "outDir": "lib" + }, + "include": ["src/tests", "src/fixtures", "src/page-objects", "src/resources"] +} diff --git a/package.json b/package.json index 77f79ed..ad675bc 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "workspaces": [ "packages/*", "applications/*", - "extensions/*" + "extensions/*", + "e2e-tests" ], "scripts": { "build": "lerna run build", @@ -19,6 +20,7 @@ "test": "jest --config=configs/jest.config.js", "theia:browser": "yarn --cwd applications/browser-app", "theia:electron": "yarn --cwd applications/electron-app", + "ui-test": "yarn --cwd ./e2e-tests/ test", "watch": "lerna run --parallel watch" }, "devDependencies": { diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index c29ef76..16b67d9 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -10,6 +10,7 @@ "applications/*/src", "applications/*/scripts", "extensions/*/src", - "extensions/*/test" + "extensions/*/test", + "e2e-tests/src/" ] } diff --git a/tsconfig.json b/tsconfig.json index 6911c60..39b92ad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,6 +35,9 @@ }, { "path": "packages/protocol" + }, + { + "path": "e2e-tests" } ] } diff --git a/yarn.lock b/yarn.lock index b63157a..fdc7469 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1914,6 +1914,13 @@ picocolors "^1.0.0" tslib "^2.6.0" +"@playwright/test@^1.37.1": + 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" + "@popperjs/core@^2.11.8": version "2.11.8" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" @@ -2423,6 +2430,14 @@ "@theia/request" "1.43.1" semver "^7.5.4" +"@theia/playwright@1.43.1": + version "1.43.1" + resolved "https://registry.yarnpkg.com/@theia/playwright/-/playwright-1.43.1.tgz#283b60c16b20b2ca4f3d206cb1f3df3addb41677" + integrity sha512-g7CVN9rXJCRXN69m/GEo2VfAnvOupFgtMyIwS9VApmDbUI6oaVu159ekIiFxPI2pIReIbOsaeYlaJm7EWIPxHw== + dependencies: + "@playwright/test" "^1.37.1" + fs-extra "^9.0.8" + "@theia/plugin-ext-vscode@1.43.1": version "1.43.1" resolved "https://registry.yarnpkg.com/@theia/plugin-ext-vscode/-/plugin-ext-vscode-1.43.1.tgz#4921f70116af1953af6bd93b9d36e9fa0c316166" @@ -3939,6 +3954,20 @@ ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.3: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +allure-js-commons@2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/allure-js-commons/-/allure-js-commons-2.9.2.tgz#47a2e31d1e476aa565fd4c467e6e1f3540774f6a" + integrity sha512-Qvi+zMZQruklqcnqG/zHCnE209v1YiWGhO3H2aPW2aXC8Ockqd01a+w2lP4Qqp3SfC+WQDeAK2+pp+v+eNl8xQ== + dependencies: + properties "^1.2.1" + +allure-playwright@^2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/allure-playwright/-/allure-playwright-2.9.2.tgz#27e86f37921a456632830e9c9820188ad5844aad" + integrity sha512-N0X1c1GGLg74vdDAuq86KCekuvQ5BaqqpgcBpJj5x3y/RlQPBn84wlg8PT/ViKQM4EdbNFMXOXpa5Opufv6qCg== + dependencies: + allure-js-commons "2.9.2" + anser@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/anser/-/anser-2.1.1.tgz#8afae28d345424c82de89cc0e4d1348eb0c5af7c" @@ -7450,7 +7479,7 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: +fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.0.8: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -7488,6 +7517,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -11221,6 +11255,20 @@ pkginfo@0.4.1: resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" integrity sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ== +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@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" + plist@^3.0.1, plist@^3.0.4: version "3.1.0" resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" @@ -11432,6 +11480,11 @@ prop-types@^15.5.0, prop-types@^15.5.6, prop-types@^15.6.1, prop-types@^15.6.2, object-assign "^4.1.1" react-is "^16.13.1" +properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/properties/-/properties-1.2.1.tgz#0ee97a7fc020b1a2a55b8659eda4aa8d869094bd" + integrity sha512-qYNxyMj1JeW54i/EWEFsM1cVwxJbtgPp8+0Wg9XjNaK6VE/c4oRi6PNu5p7w1mNXEIQIjV5Wwn8v8Gz82/QzdQ== + property-expr@^2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8"