From 0527167889d0c3ab5b9b0e95f66c2cc6628bd770 Mon Sep 17 00:00:00 2001 From: Pixel998 Date: Fri, 31 Jan 2025 10:20:53 +0300 Subject: [PATCH 1/4] feat: add Playwright for e2e testing --- .github/workflows/applications.yml | 54 +++- generators/client/command.js | 14 +- generators/client/entity-files.js | 38 ++- generators/client/files.js | 60 ++-- generators/client/index.js | 50 +++- generators/client/templates/.env.test.ejs | 4 + .../templates/.gitignore.jhi.svelte.ejs | 44 ++- .../templates/.prettierignore.jhi.svelte.ejs | 11 + .../client/templates/README.md.jhi.svelte.ejs | 6 +- .../client/templates/cypress/package.json | 11 + .../client/templates/eslint.config.js.ejs | 19 +- .../templates/package-template.json.ejs | 5 +- generators/client/templates/package.json | 4 +- .../templates/playwright.config.cjs.ejs | 82 ++++++ .../templates/playwright/auth.setup.js.ejs | 20 ++ .../playwright/fixtures/integration-test.png | Bin 0 -> 5423 bytes .../account/change-password.spec.js.ejs | 93 ++++++ .../integration/account/register.spec.js.ejs | 131 +++++++++ .../account/reset/init-password.spec.js.ejs | 55 ++++ .../integration/account/settings.spec.js.ejs | 91 ++++++ .../integration/admin/gateway.spec.js.ejs | 30 ++ .../integration/admin/logger.spec.js.ejs | 151 ++++++++++ .../user-management/user-create.spec.js.ejs | 112 +++++++ .../user-management/user-delete.spec.js.ejs | 68 +++++ .../user-management/user-list.spec.js.ejs | 101 +++++++ .../user-management/user-update.spec.js.ejs | 124 ++++++++ .../user-management/user-view.spec.js.ejs | 52 ++++ .../entities/entity/entity-create.spec.js.ejs | 116 ++++++++ .../entities/entity/entity-delete.spec.js.ejs | 123 ++++++++ .../entities/entity/entity-list.spec.js.ejs | 178 +++++++++++ .../entities/entity/entity-update.spec.js.ejs | 256 ++++++++++++++++ .../entities/entity/entity-view.spec.js.ejs | 104 +++++++ .../playwright/integration/footer.spec.js.ejs | 16 + .../playwright/integration/home.spec.js.ejs | 50 ++++ .../playwright/integration/login.spec.js.ejs | 74 +++++ .../playwright/integration/navbar.spec.js.ejs | 178 +++++++++++ .../playwright/integration/routes.spec.js.ejs | 114 +++++++ .../client/templates/playwright/package.json | 12 + .../utils/entities/entity-util.js.ejs | 242 +++++++++++++++ .../playwright/utils/test-utils.js.ejs | 277 ++++++++++++++++++ .../husky/templates/.lintstagedrc.cjs.ejs | 2 +- 41 files changed, 3098 insertions(+), 74 deletions(-) create mode 100644 generators/client/templates/.env.test.ejs create mode 100644 generators/client/templates/cypress/package.json create mode 100644 generators/client/templates/playwright.config.cjs.ejs create mode 100644 generators/client/templates/playwright/auth.setup.js.ejs create mode 100644 generators/client/templates/playwright/fixtures/integration-test.png create mode 100644 generators/client/templates/playwright/integration/account/change-password.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/account/register.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/account/reset/init-password.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/account/settings.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/admin/gateway.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/admin/logger.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/admin/user-management/user-create.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/admin/user-management/user-list.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/footer.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/home.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/login.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/navbar.spec.js.ejs create mode 100644 generators/client/templates/playwright/integration/routes.spec.js.ejs create mode 100644 generators/client/templates/playwright/package.json create mode 100644 generators/client/templates/playwright/utils/entities/entity-util.js.ejs create mode 100644 generators/client/templates/playwright/utils/test-utils.js.ejs diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index 52ca58569..db487408e 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -47,6 +47,7 @@ jobs: ] os: [ubuntu-latest] node_version: [20.x] + test_framework: ['cypress', 'playwright'] steps: - uses: actions/checkout@v4 - name: Download latest chrome binary @@ -79,6 +80,11 @@ jobs: ~/.cache/Cypress/13.5.x ~/.cache/Cypress/cy key: ${{ runner.os }}-cypress-13.5.x + - name: Configure playwright cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-1.49.x - name: Retrieve saved Svelte Docker Image uses: actions/download-artifact@v4 with: @@ -99,13 +105,13 @@ jobs: run: | cd "$HOME/svelte-app" cp $TEST_SCRIPTS_PATH/${{ matrix.apps }} .yo-rc.json - docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA --no-insight --force --jest --swagger-ui + docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA --no-insight --force --jest --swagger-ui --test-framework ${{ matrix.test_framework }} - name: Generate application if: contains(matrix.apps, '.jdl') run: | cd "$HOME/svelte-app" cp $TEST_SCRIPTS_PATH/${{ matrix.apps }} app.jdl - docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA import-jdl app.jdl --no-insight --force + docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA import-jdl app.jdl --no-insight --force --test-framework ${{ matrix.test_framework }} - name: Maven - Unit tests if: contains(matrix.apps , 'maven') run: | @@ -134,20 +140,22 @@ jobs: run: | cd "$HOME/svelte-app" docker run --rm -v $PWD:/app -v $HOME/.gradle:/home/jsvelte/.gradle --entrypoint ./gradlew jhipster/jhipster-svelte:$GITHUB_SHA bootJar --no-daemon - - name: Maven - Cypress tests + - name: Maven - E2E tests if: contains(matrix.apps , 'maven') run: | cd "$HOME/svelte-app" docker run -d --rm -v $PWD:/app -v $HOME/.m2:/home/jsvelte/.m2 -p8080:8080 --entrypoint ./mvnw jhipster/jhipster-svelte:$GITHUB_SHA -DskipTests -Dspring-boot.run.jvmArguments="-Dspring.docker.compose.enabled=false" timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} npm run e2e:ci - - name: Gradle Cypress tests + - name: Gradle - E2E tests if: contains(matrix.apps, 'gradle') run: | cd "$HOME/svelte-app" docker run --rm -v $PWD:/app -v $HOME/.gradle:/home/jsvelte/.gradle --entrypoint ./gradlew jhipster/jhipster-svelte:$GITHUB_SHA clean docker run -d --rm -v $PWD:/app -v $HOME/.gradle:/home/jsvelte/.gradle -p8080:8080 --entrypoint ./gradlew jhipster/jhipster-svelte:$GITHUB_SHA -x test timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} npm run e2e:ci development-profile-non-docker-builds: name: ${{ matrix.apps }} @@ -162,6 +170,7 @@ jobs: apps: ['monolithic-session-gradle.json', 'monolithic-jwt-gradle.json'] os: [ubuntu-latest] node_version: [20.x] + test_framework: ['cypress', 'playwright'] steps: - uses: actions/checkout@v4 - name: Download latest chrome binary @@ -198,6 +207,11 @@ jobs: ~/.cache/Cypress/13.5.x ~/.cache/Cypress/cy key: ${{ runner.os }}-cypress-13.5.x + - name: Configure playwright cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-1.49.x - name: Prepare environment run: | mkdir -p "$HOME/svelte-app" @@ -213,7 +227,7 @@ jobs: cd "$HOME/svelte-app" cp $TEST_SCRIPTS_PATH/${{ matrix.apps }} .yo-rc.json npm link "generator-jhipster-svelte" - jsvelte --no-insight --force --jest --swagger-ui + jsvelte --no-insight --force --jest --swagger-ui --test-framework ${{ matrix.test_framework }} - name: Maven - Unit tests if: contains(matrix.apps , 'maven') run: | @@ -242,19 +256,21 @@ jobs: run: | cd "$HOME/svelte-app" ./gradlew bootJar --no-daemon - - name: Maven - Cypress tests + - name: Maven - E2E tests if: contains(matrix.apps , 'maven') run: | cd "$HOME/svelte-app" ./mvnw -svelte:$GITHUB_SHA -DskipTests & timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} npm run e2e:ci - - name: Gradle Cypress tests + - name: Gradle - E2E tests if: contains(matrix.apps, 'gradle') run: | cd "$HOME/svelte-app" ./gradlew -x test & timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} npm run e2e:ci production-profile-builds: @@ -278,6 +294,7 @@ jobs: ] os: [ubuntu-latest] node_version: [20.x] + test_framework: ['cypress', 'playwright'] steps: - uses: actions/checkout@v4 - name: Download latest chrome binary @@ -314,6 +331,11 @@ jobs: ~/.cache/Cypress/13.5.x ~/.cache/Cypress/cy key: ${{ runner.os }}-cypress-13.5.x + - name: Configure playwright cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-1.49.x - name: Retrieve saved Svelte Docker Image uses: actions/download-artifact@v4 with: @@ -334,13 +356,13 @@ jobs: run: | cd "$HOME/svelte-app" cp $TEST_SCRIPTS_PATH/${{ matrix.apps }} .yo-rc.json - docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA --no-insight --force + docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA --no-insight --force --test-framework ${{ matrix.test_framework }} - name: Generate application if: contains(matrix.apps, '.jdl') run: | cd "$HOME/svelte-app" cp $TEST_SCRIPTS_PATH/${{ matrix.apps }} app.jdl - docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA import-jdl app.jdl --no-insight --force + docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA import-jdl app.jdl --no-insight --force --test-framework ${{ matrix.test_framework }} - name: Maven - Unit tests if: contains(matrix.apps , 'maven') run: | @@ -388,12 +410,13 @@ jobs: sudo echo "127.0.0.1 keycloak" | sudo tee -a /etc/hosts docker compose -f src/main/docker/keycloak.yml up -d timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:9080/realms/master)" != "200" ]]; do sleep 5; done' || false - - name: Run Cypress tests + - name: Run E2E tests run: | cd "$HOME/svelte-app" sudo echo "127.0.0.1 keycloak" | sudo tee -a /etc/hosts docker compose -f src/main/docker/app.yml up -d timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} npm run e2e:ci microservices-production-profile-builds: needs: svelte-image @@ -410,6 +433,7 @@ jobs: apps: ['microservices-oidc-maven-prod.jdl'] os: [ubuntu-latest] node_version: [20.x] + test_framework: ['cypress', 'playwright'] steps: - uses: actions/checkout@v4 - name: Download latest chrome binary @@ -446,6 +470,11 @@ jobs: ~/.cache/Cypress/13.5.x ~/.cache/Cypress/cy key: ${{ runner.os }}-cypress-13.5.x + - name: Configure playwright cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-1.49.x - name: Retrieve saved Svelte Docker Image uses: actions/download-artifact@v4 with: @@ -466,7 +495,7 @@ jobs: run: | cd "$HOME/svelte-ms" cp $TEST_SCRIPTS_PATH/${{ matrix.apps }} app.jdl - docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA import-jdl app.jdl --no-insight --skip-checks --force + docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA import-jdl app.jdl --no-insight --skip-checks --force --test-framework ${{ matrix.test_framework }} - name: Maven - Unit tests - Gateway if: contains(matrix.apps , 'maven') run: | @@ -546,13 +575,14 @@ jobs: cd "$HOME/svelte-ms/docker-compose" docker compose up -d keycloak timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:9080/realms/master)" != "200" ]]; do sleep 5; done' || false - - name: Run Cypress tests + - name: Run E2E tests run: | sudo echo "127.0.0.1 keycloak" | sudo tee -a /etc/hosts cd "$HOME/svelte-ms/docker-compose" docker compose up -d timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false cd "$HOME/svelte-ms/gateway" + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} # npm run e2e:ci lighthouse-job: needs: svelte-image diff --git a/generators/client/command.js b/generators/client/command.js index d203bce55..c10f5aea6 100644 --- a/generators/client/command.js +++ b/generators/client/command.js @@ -1,5 +1,6 @@ -const command = { - configs: {}, +import { asCommand } from 'generator-jhipster'; + +export default asCommand({ options: { jest: { description: 'Jest JavaScript unit testing framework', @@ -11,7 +12,10 @@ const command = { type: Boolean, scope: 'blueprint', }, + testFramework: { + description: 'E2E testing framework to use', + type: String, + scope: 'blueprint', + }, }, -}; - -export default command; +}); diff --git a/generators/client/entity-files.js b/generators/client/entity-files.js index 63a1cc0ed..c610aaf27 100644 --- a/generators/client/entity-files.js +++ b/generators/client/entity-files.js @@ -55,30 +55,37 @@ export default { { templates: [ { - file: 'cypress/integration/entities/entity/entity-delete.spec.js', + file: generator => `${generator.testFramework}/integration/entities/entity/entity-delete.spec.js`, renameTo: generator => - `cypress/integration/entities/${generator.entityFolderName}/${generator.entityFileName}-delete.spec.js`, + `${generator.testFramework}/integration/entities/${generator.entityFolderName}/${generator.entityFileName}-delete.spec.js`, }, { - file: 'cypress/integration/entities/entity/entity-list.spec.js', + file: generator => `${generator.testFramework}/integration/entities/entity/entity-list.spec.js`, renameTo: generator => - `cypress/integration/entities/${generator.entityFolderName}/${generator.entityFileName}-list.spec.js`, + `${generator.testFramework}/integration/entities/${generator.entityFolderName}/${generator.entityFileName}-list.spec.js`, }, { - file: 'cypress/integration/entities/entity/entity-view.spec.js', + file: generator => `${generator.testFramework}/integration/entities/entity/entity-view.spec.js`, renameTo: generator => - `cypress/integration/entities/${generator.entityFolderName}/${generator.entityFileName}-view.spec.js`, + `${generator.testFramework}/integration/entities/${generator.entityFolderName}/${generator.entityFileName}-view.spec.js`, }, { - file: 'cypress/integration/entities/entity/entity-create.spec.js', + file: generator => `${generator.testFramework}/integration/entities/entity/entity-create.spec.js`, renameTo: generator => - `cypress/integration/entities/${generator.entityFolderName}/${generator.entityFileName}-create.spec.js`, + `${generator.testFramework}/integration/entities/${generator.entityFolderName}/${generator.entityFileName}-create.spec.js`, }, { - file: 'cypress/integration/entities/entity/entity-update.spec.js', + file: generator => `${generator.testFramework}/integration/entities/entity/entity-update.spec.js`, renameTo: generator => - `cypress/integration/entities/${generator.entityFolderName}/${generator.entityFileName}-update.spec.js`, + `${generator.testFramework}/integration/entities/${generator.entityFolderName}/${generator.entityFileName}-update.spec.js`, }, + ], + }, + ], + entityCypressUtils: [ + { + condition: generator => generator.testFramework === 'cypress', + templates: [ { file: 'cypress/support/entities/entity-util.js', renameTo: generator => `cypress/support/entities/${generator.entityFileName}-util.js`, @@ -86,4 +93,15 @@ export default { ], }, ], + entityPlaywrightUtils: [ + { + condition: generator => generator.testFramework === 'playwright', + templates: [ + { + file: 'playwright/utils/entities/entity-util.js', + renameTo: generator => `playwright/utils/entities/${generator.entityFileName}-utils.js`, + }, + ], + }, + ], }; diff --git a/generators/client/files.js b/generators/client/files.js index 20bf8c703..5c7946e02 100644 --- a/generators/client/files.js +++ b/generators/client/files.js @@ -12,7 +12,6 @@ const svelteFiles = { '.npmrc', '.gitignore.jhi.svelte', '.prettierignore.jhi.svelte', - 'cypress.config.cjs', 'eslint.config.js', 'README.md.jhi.svelte', { @@ -27,50 +26,69 @@ const svelteFiles = { ], }, ], + cypress: [ + { + condition: generator => generator.testFramework === 'cypress', + templates: [ + 'cypress.config.cjs', + 'cypress/plugins/index.cjs', + 'cypress/support/index.js', + 'cypress/support/commands.js', + ], + }, + ], + playwright: [ + { + condition: generator => generator.testFramework === 'playwright', + templates: [ + 'playwright.config.cjs', + '.env.test', + 'playwright/utils/test-utils.js', + 'playwright/auth.setup.js', + ], + }, + ], e2e: [ { templates: [ { file: generator => `${FRONTEND_SRC_DIR}static/content/img/${generator.hipster}_head-192.png`, - renameTo: () => `cypress/fixtures/integration-test.png`, + renameTo: generator => `${generator.testFramework}/fixtures/integration-test.png`, method: 'copy', }, - 'cypress/integration/footer.spec.js', - 'cypress/integration/home.spec.js', - 'cypress/integration/navbar.spec.js', - 'cypress/integration/routes.spec.js', - 'cypress/integration/admin/logger.spec.js', - 'cypress/plugins/index.cjs', - 'cypress/support/index.js', - 'cypress/support/commands.js', + generator => `${generator.testFramework}/integration/footer.spec.js`, + generator => `${generator.testFramework}/integration/home.spec.js`, + generator => `${generator.testFramework}/integration/navbar.spec.js`, + generator => `${generator.testFramework}/integration/routes.spec.js`, + generator => `${generator.testFramework}/integration/admin/logger.spec.js`, ], }, ], e2eGateway: [ { condition: generator => generator.applicationType === 'gateway', - templates: ['cypress/integration/admin/gateway.spec.js'], + templates: [generator => `${generator.testFramework}/integration/admin/gateway.spec.js`], }, ], e2eLogin: [ { condition: generator => generator.authenticationType !== 'oauth2', - templates: ['cypress/integration/login.spec.js'], + templates: [generator => `${generator.testFramework}/integration/login.spec.js`], }, ], e2eUserManagement: [ { condition: generator => !generator.skipUserManagement && generator.authenticationType !== 'oauth2', templates: [ - 'cypress/integration/account/change-password.spec.js', - 'cypress/integration/account/register.spec.js', - 'cypress/integration/account/settings.spec.js', - 'cypress/integration/account/reset/init-password.spec.js', - 'cypress/integration/admin/user-management/user-create.spec.js', - 'cypress/integration/admin/user-management/user-delete.spec.js', - 'cypress/integration/admin/user-management/user-list.spec.js', - 'cypress/integration/admin/user-management/user-update.spec.js', - 'cypress/integration/admin/user-management/user-view.spec.js', + generator => `${generator.testFramework}/integration/account/change-password.spec.js`, + generator => `${generator.testFramework}/integration/account/register.spec.js`, + generator => `${generator.testFramework}/integration/account/settings.spec.js`, + generator => `${generator.testFramework}/integration/account/reset/init-password.spec.js`, + generator => `${generator.testFramework}/integration/admin/user-management/user-create.spec.js`, + generator => `${generator.testFramework}/integration/admin/user-management/user-delete.spec.js`, + generator => `${generator.testFramework}/integration/admin/user-management/user-list.spec.js`, + generator => `${generator.testFramework}/integration/admin/user-management/user-update.spec.js`, + generator => `${generator.testFramework}/integration/admin/user-management/user-view.spec.js`, ], }, ], diff --git a/generators/client/index.js b/generators/client/index.js index 8ebe852f6..7f49a3add 100644 --- a/generators/client/index.js +++ b/generators/client/index.js @@ -32,11 +32,39 @@ export default class extends ClientGenerator { async parseCommand() { await this.parseCurrentJHipsterCommand(); }, + loadConfigFromJHipster() { + if (this.options.testFramework) { + this.blueprintStorage.defaults({ testFramework: this.options.testFramework }); + } + }, }); } get [BaseApplicationGenerator.PROMPTING]() { return this.asPromptingTaskGroup({ + async promptForTestFramework() { + await this.prompt( + [ + { + type: 'list', + name: 'testFramework', + message: 'Which E2E testing framework would you like to use?', + choices: [ + { + name: 'Cypress', + value: 'cypress', + }, + { + name: 'Playwright', + value: 'playwright', + }, + ], + }, + ], + this.blueprintStorage, + ); + this.blueprintStorage.defaults({ testFramework: 'cypress' }); + }, clientConfigurations() { this.clientFramework = this.jhipsterConfig.clientFramework = 'svelte'; this.jhipsterConfig.clientTheme = this.clientTheme = 'none'; @@ -58,11 +86,15 @@ export default class extends ClientGenerator { if (this.blueprintConfig.jest === undefined) { this.blueprintConfig.jest = false; } + if (this.blueprintConfig.testFramework === undefined) { + this.blueprintConfig.testFramework = 'cypress'; + } } }, setLocalCommandOptions() { this.jest = this.blueprintConfig.jest; this.swaggerUi = this.blueprintConfig.swaggerUi; + this.testFramework = this.blueprintConfig.testFramework; }, }); } @@ -212,7 +244,12 @@ export default class extends ClientGenerator { async writingTemplateTask({ application }) { await this.writeFiles({ sections: svelteFiles, - context: { ...application, swaggerUi: this.swaggerUi, jest: this.jest }, + context: { + ...application, + swaggerUi: this.swaggerUi, + jest: this.jest, + testFramework: this.testFramework, + }, }); }, }); @@ -253,7 +290,13 @@ export default class extends ClientGenerator { for (const entity of entities.filter(entity => !entity.skipClient && !entity.builtIn)) { await this.writeFiles({ sections: entitySvelteFiles, - context: { ...application, ...entity, swaggerUi: this.swaggerUi, jest: this.jest }, + context: { + ...application, + ...entity, + swaggerUi: this.swaggerUi, + jest: this.jest, + testFramework: this.testFramework, + }, }); } }, @@ -309,6 +352,9 @@ export default class extends ClientGenerator { const unitPackageJson = JSON.parse(this.readTemplate(`${unitFrameworkType}/package.json`)); this.packageJson.merge(unitPackageJson); + const e2ePackageJson = JSON.parse(this.readTemplate(`${this.testFramework}/package.json`)); + this.packageJson.merge(e2ePackageJson); + const javaPrettierPackageJson = JSON.parse(this.readTemplate(`java-prettier/package.json`)); this.packageJson.merge(javaPrettierPackageJson); diff --git a/generators/client/templates/.env.test.ejs b/generators/client/templates/.env.test.ejs new file mode 100644 index 000000000..072adb996 --- /dev/null +++ b/generators/client/templates/.env.test.ejs @@ -0,0 +1,4 @@ +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin +USER_USERNAME=user +USER_PASSWORD=user \ No newline at end of file diff --git a/generators/client/templates/.gitignore.jhi.svelte.ejs b/generators/client/templates/.gitignore.jhi.svelte.ejs index 34ad5ba14..f2770996a 100644 --- a/generators/client/templates/.gitignore.jhi.svelte.ejs +++ b/generators/client/templates/.gitignore.jhi.svelte.ejs @@ -12,18 +12,34 @@ See the License for the specific language governing permissions and limitations under the License. -%> - <&_ if (!fragment.section) { -&> - ###################### - # Cypress - ###################### - /cypress/screenshots/ - /cypress/fixtures/example.json - /cypress/downloads - /cypress/videos +<&_ if (!fragment.section) { -&> +<%_ if (this.testFramework === 'cypress') { _%> +###################### +# Cypress +###################### +/cypress/screenshots/ +/cypress/fixtures/example.json +/cypress/downloads +/cypress/videos +<%_ } else { _%> +###################### +# Env +###################### +.env.test - ###################### - # SvelteKit - ###################### - vite.config.js.timestamp-* - vite.config.ts.timestamp-* - <&_ } -&> +###################### +# Playwright +###################### +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.auth/ +/playwright/.cache/ +<%_ } _%> + +###################### +# SvelteKit +###################### +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +<&_ } -&> diff --git a/generators/client/templates/.prettierignore.jhi.svelte.ejs b/generators/client/templates/.prettierignore.jhi.svelte.ejs index 907801bbb..56e11b72d 100644 --- a/generators/client/templates/.prettierignore.jhi.svelte.ejs +++ b/generators/client/templates/.prettierignore.jhi.svelte.ejs @@ -13,6 +13,7 @@ limitations under the License. -%> <&_ if (!fragment.section) { -&> +<%_ if (this.testFramework === 'cypress') { _%> ###################### # Cypress ###################### @@ -20,6 +21,16 @@ /cypress/fixtures/example.json /cypress/downloads /cypress/videos +<%_ } else { _%> +###################### +# Playwright +###################### +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.auth/ +/playwright/.cache/ +<%_ } _%> ###################### # SvelteKit diff --git a/generators/client/templates/README.md.jhi.svelte.ejs b/generators/client/templates/README.md.jhi.svelte.ejs index dccca10e0..2ecf618a6 100644 --- a/generators/client/templates/README.md.jhi.svelte.ejs +++ b/generators/client/templates/README.md.jhi.svelte.ejs @@ -86,10 +86,14 @@ npm test ``` - UI end-to-end tests are powered by [Cypress][]. They're located under cypress directory and can be run by starting Spring Boot in one terminal (`./mvnw spring-boot:run`) and running the tests (`npm run e2e`) in a second one. + UI end-to-end tests are powered by [<%_ this.testFramework === 'cypress' ? 'Cypress' : 'Playwright' _%>][]. They're located under <%= this.testFramework %> directory and can be run by starting Spring Boot in one terminal (`./mvnw spring-boot:run`) and running the tests (`npm run e2e`) in a second one. <&_ } -&> <&_ if (fragment.referenceSection) { -&> [Jest]: https://facebook.github.io/jest/ + <%_ if (this.testFramework === 'cypress') { _%> [Cypress]: https://www.cypress.io/ + <%_ } else { _%> + [Playwright]: https://playwright.dev/ + <%_ } _%> <&_ } -&> diff --git a/generators/client/templates/cypress/package.json b/generators/client/templates/cypress/package.json new file mode 100644 index 000000000..7748e9fab --- /dev/null +++ b/generators/client/templates/cypress/package.json @@ -0,0 +1,11 @@ +{ + "scripts": { + "e2e": "npm run e2e:ci -- --headed", + "e2e:ci": "cypress run --browser chrome", + "cy:open": "cypress open --e2e --browser chrome" + }, + "devDependencies": { + "cypress": "13.5.1", + "eslint-plugin-cypress": "4.0.0" + } +} diff --git a/generators/client/templates/eslint.config.js.ejs b/generators/client/templates/eslint.config.js.ejs index f7228ee21..840fcdb92 100644 --- a/generators/client/templates/eslint.config.js.ejs +++ b/generators/client/templates/eslint.config.js.ejs @@ -1,5 +1,9 @@ import svelte from "eslint-plugin-svelte"; +<%_ if (this.testFramework === 'cypress') { _%> import cypress from "eslint-plugin-cypress"; +<%_ } else { _%> +import playwright from 'eslint-plugin-playwright' +<%_ } _%> import jestDom from "eslint-plugin-jest-dom"; import globals from "globals"; import path from "node:path"; @@ -29,12 +33,16 @@ export default [{ }, ...compat.extends( "eslint:recommended", "plugin:svelte/recommended", + <%_ if (this.testFramework === 'cypress') { _%> "plugin:cypress/recommended", + <%_ } _%> "plugin:jest-dom/recommended", ), { plugins: { svelte, + <%_ if (this.testFramework === 'cypress') { _%> cypress, + <%_ } _%> "jest-dom": jestDom, }, @@ -42,7 +50,9 @@ export default [{ globals: { ...globals.browser, ...globals.node, + <%_ if (this.testFramework === 'cypress') { _%> ...cypress.environments.globals.globals, + <%_ } _%> <%_ if (this.blueprintConfig.jest) { _%> "jest": true, <%_ } else { _%> @@ -57,5 +67,12 @@ export default [{ rules: { "no-unused-vars": "off", }, -}]; +}, +<%_ if (this.testFramework === 'playwright') { _%> + { + ...playwright.configs['flat/recommended'], + files: ['playwright/**'], + } +<%_ } _%> +]; diff --git a/generators/client/templates/package-template.json.ejs b/generators/client/templates/package-template.json.ejs index 10fd640d1..568f2c5f4 100644 --- a/generators/client/templates/package-template.json.ejs +++ b/generators/client/templates/package-template.json.ejs @@ -5,7 +5,7 @@ "private": true, "license": "UNLICENSED", "scripts": { - "format": "prettier --write \"{,src/**/,cypress/**/}*.{md,json,js,svelte,css,html,yml}\"", + "format": "prettier --write \"{,src/**/,<%= this.testFramework %>/**/}*.{md,json,js,svelte,css,html,yml}\"", "lint": "eslint .", "prepare": "husky", "lint:fix": "npm run lint --fix", @@ -14,9 +14,6 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", - "e2e": "npm run e2e:ci -- --headed", - "e2e:ci": "cypress run --browser chrome", - "cy:open": "cypress open --e2e --browser chrome", "build": "vite build", "webapp:build": "npm run build", "webapp:prod": "npm run build", diff --git a/generators/client/templates/package.json b/generators/client/templates/package.json index 204bc0ec8..243413729 100644 --- a/generators/client/templates/package.json +++ b/generators/client/templates/package.json @@ -10,11 +10,9 @@ "@testing-library/jest-dom": "6.6.2", "@testing-library/svelte": "5.2.3", "@testing-library/user-event": "14.5.2", - "autoprefixer": "10.4.20", - "cypress": "13.5.1", + "autoprefixer": "10.4.20", "esbuild": "0.24.0", "eslint": "9.12.0", - "eslint-plugin-cypress": "4.0.0", "eslint-plugin-jest-dom": "5.4.0", "eslint-plugin-svelte": "2.44.1", "globals": "15.11.0", diff --git a/generators/client/templates/playwright.config.cjs.ejs b/generators/client/templates/playwright.config.cjs.ejs new file mode 100644 index 000000000..5ef3383de --- /dev/null +++ b/generators/client/templates/playwright.config.cjs.ejs @@ -0,0 +1,82 @@ +const { defineConfig, devices } = require('@playwright/test') + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +require('dotenv').config({ path: '.env.test' }) + +/** + * See https://playwright.dev/docs/test-configuration. + */ +module.exports = defineConfig({ + testDir: 'playwright', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:8080', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'setup', + testMatch: /.*\.setup\.js/, + }, + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + dependencies: ['setup'], + }, + + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}) diff --git a/generators/client/templates/playwright/auth.setup.js.ejs b/generators/client/templates/playwright/auth.setup.js.ejs new file mode 100644 index 000000000..5daa2d957 --- /dev/null +++ b/generators/client/templates/playwright/auth.setup.js.ejs @@ -0,0 +1,20 @@ +import { test as setup } from '@playwright/test' +import { loginByApi } from './utils/test-utils' + +const adminFile = 'playwright/.auth/admin.json' + +setup('authenticate as admin', async ({ page }) => { + await page.goto('/') + await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) + await page.context().storageState({ path: adminFile }) +}) + +<%_ if (!skipUserManagement || authenticationType === 'oauth2') { _%> + const userFile = 'playwright/.auth/user.json' + + setup('authenticate as user', async ({ page }) => { + await page.goto('/') + await loginByApi(page, process.env.USER_USERNAME, process.env.USER_PASSWORD) + await page.context().storageState({ path: userFile }) + }) +<%_ } _%> diff --git a/generators/client/templates/playwright/fixtures/integration-test.png b/generators/client/templates/playwright/fixtures/integration-test.png new file mode 100644 index 0000000000000000000000000000000000000000..24baf78ce8326c3c2756f10ebed90829d0bddf30 GIT binary patch literal 5423 zcmZ{oby(Bg+rYmYJ!;fw8AvHLLU829=n)&GAP)@b?hpk=Dvbz8OGzjV3I;M7K?Oww z1(8OiyW!>cyw~q}pMTzSopYbh9p}F86MvmJoS`;7gbM-y0KKk`hVjKr`p=*yyO0jx zylVge-o)vfYF?=7>V~Q)BQ>;vlA3{vI{u<7sTr!N<1{pkHFeF@&_*bABUN>rD#k<= zjYBD@pf!w@P=<=C2GUZpNCj0Tlo3kPR1Jg2Xq#NbYh%r?E9>g%np}XEp1GbT-cZv> zThB~O*A%U7si>-dOrLbZzO8ZpvR$ zLopgvUe~vBapx~>X(HE8Qu+d#+p;}4TJufz#OU9h!>GH5Zcvxs~R`zaQ zK3BD^W21-}DfbK0<5WzYmvhbbat-U#LoODgqTi{f3?d(VP)how6yN_f%icDrUNT`s zD|?FjA}uQoI<7}8uFor}A|f?cW?g&v$bE_nOY}@ zdz+D%liuvJ>F(s@#4cJ3s$tM&@A{>>Ddf9*i|cOy(yG(|)?~Po&5i45qK5*Um4jfH5H;H6Acflpqb# z4V+`tt?ZOFZB?~vL=n-JVTjBhGER@A1FFS>>Sg_&UH2@qv5GOWPjfZBV;J7d89|Z@ zZqjhgQ;6)muBg4s%6vvkF(fLz!pCK$s@-dV3{l72L8`1t3$98ET$H+eiF=uw^FN(~ zV_iXbNrZ1jfETZ(uXP=B`22a$#iKv=H#Rf@V5oZ^_s;RxCGb`*tLML{tCO=B1TPNy zqP-|Moa5`-Ncvyx-*Wih%KyW=d*^R${`URK`@fKbe=v>sH^TlgApRl4m4EPWXdF^M z_db96H~0M4b#{LGU;NjcaKS-i|1$iI3l~zXivB)}v1@bQ(Bc|LxXRSDaP!V{0Dx}k zYM@PnL0?B>l6T-xs*TExUsqTE0DfWX=ZQ}yjtUgWe@jhLBXZJgEUeQZ&%|M7r#5Eu zAB$IC%MUs;yuQRPCrQUE+SxnuNO?%`y3?FUiYR~^l<(KdBgHd&BQpLGsa z^o{MC1NN_TAzH&<+K z^$#dPA@s)B7tF8yIBK;bg~>@=87sqjqlD^CsYtXFE)<+NUIsy^Y=hVN$P4(763rfi zZ@R`shG$&rqSWGmPkRc?$Dv;^ehdlGI*jjplSaMYv#${&mW0BpeU458dOkkbfIdhk zRPm5wdB<_Yj)w{|q|P{XYg52d4^0aR)%dlwd0pf4eSgzg$2^-dgf`P9n(VeIO!(p} zLAg|>0@Rok>+Hdd>JDNcLpnkP%|Cb}azq_NR}Q8G8%3vgHh>)&AJ>8_0pU%5Z~=sa z?4-?Kgv*KiI#U6|l>#<|hqYp?3CPM;LN{_mqxNlfcMez@8a>@2|2QB>Ap0*DH6L>L7+gSRw7*{~^FQDq7^XrK1b}9ud{{q#Qd~EZl3Q81P5_ZOl z?vgnuog8*T&*V)_xc>^=N8SWd7ZS|)`k`q_-<1uX+XFsOL*Do*y9WeO!}m3p`7-hb z2e~M!;WcS&^y)e9d_|^>_Abwbq96wtL9%F(pX!xX*{AEVTsC*4`pU$aRS#J^DAkyi`)4J=%%(J)`19`PoS1rb+L5SHH`w zh}9YIS3@q`$W&r{?3E6KOM{%}2FxKEmoxO7`a{%NL5`5Gf?N@`uR71N-?5d7Bh045 zqww@HyMekRLRc54Z3^s{5oW&FLRjF3mk|V=kmx5Bh{U+ zV{TE(jvL!k1+JDlmKByF{E~03%9`4_$hNF0z+J+J6%q#J9~Cq!@feo>oDaO4)ABa3 zxpz^U)Fd>uskmA_v>N!vO0ss;wtT0@z|nO6N}B zHoWaTrU%oD62+H``!Ijcc~X@v-&>FnbdRo2{Fs>gVTLV7)Tz(fm&%v{zG?9=uB4}etPx;sgiFt;t&CjcmL>gix(Di4jFFI;+rtHe2GAW-N|!Iir1U4)N=q<3g#;gS}+9FG2&bP_k2;_1&H3ye0L#Yzwf zTnb2ZPg2#HRL!6p5=&fm>FbBKm`#ZbVniyo;%-BSW=`{&>oDPJdPGz+HWD98%D)XV z+)L;_Woe-&IFb5~xX!oDU(q{4NIL07Cdqez}sa%kq7e4KG$8qOgtptFkjzo0K>sND#!iH zC&@`Ud*$OecDIK~K9+_0Yx!}n#MPGcX@y~7FPSjLtg(T07RN$;!aituzxZfh`t0UY zzwTbuH#4-a4eWyKVu;*i&g;<)_b7hBNjUyOM$pxtQ;$JsqLs>DBabq<|Be${6*v&F3;+AKZ@FnffFhyyxkjDf4jfC`QiQW?T2&+39OW? z0l;4C&OPa*^1%c_-ATSQ7oKuMpsLI1O4X4Eg*mXXqU>39Ifp9LToH! zAkkCTwHV254ldgNq>X!(i^oz;F;Vguzm$L*Nxg4aJWTgtzeNue?h%$Rk;^fX^=RgQ zCn#n~_uIIP(ouWY?)dZeKl`2sB>xB?(2A;ZOGFVNlgsun^1zX6Xp{wip)=89Y{R+d zu+=VF0_uGWmqkrXWw+01YfM2l@gOgvL!SH#MY4`Od%Ir%GkBvy+lWyjK~D_=z&sv zfoC)eLG#3S#v}?l(lQ<6O$`RWdLHj0R_`Y?P^SI^9aay3uA*V9mJ7J105DA#kc-3@ z2f|Qjmmp5iIQjjzrvn2(%Qwb~;LqC8P^a%(Mmq&hC=jN!qBaAd<8ZAvx5J#B0Oe;C z6ROmnvF5zn#6XSHW*IO+|$~?EIjkhN*`=UKUuK@Yh)xBP|x?+WiyjJ&;ggFfou2hw!GDOqy!uuNf4l1ITfNjNlK+gToLsW z1;a-fLhGk^E`tbF^FiLv5e#?x>?!kH+l7lISJ<_N6hc%`T=NaX&HqxKT;~=-;hQoVj7Bu6+JOUJQEtLOKlfPEcm=#+9O+{iZ0t%Mqih1 zvdbEVDy^`yY8kbqScR7t5+SJZ`r@x8E2(u4QqH++o(}4?KWCPQk}@BAjNNjrPiJ)q z+B|QKj1({OE3IdOSaY_i!MlkcUHnZ4VT~iN1N>Y-6eo7+nxUY!Us4qtB~A?mJ7FSi z!MhP_lR9!-eq&iDBIX|Tu;S$LI!}{(;`b(=(wcC`bqhS)EG&Gs5m12K+0&5>K5cF9 z>zcE7b6d6_zVX5Leykb6D_J3%rZ}EDhNsDz7q5_^_@R=9s`+a=PqD!g!Pgh-u zH^y488H)6uUB4;tBE2gEJKK7uIk=u=Rp7pll3O~!MH$HMJV$Kr>F-wTRu0yb5z^w^ z>Iw2(;MUIr)z&GLUirzK2ILwe3@6)vb_V6`epYHg_#eK5uIvRFS$XbNm1xJWv7Q6IS2SfE5J}!S=6wx5Q$r*|a zpGphkDn8RMrE}im7&^(;0(GmX!44}A2ioWmS08W`?xtZ4d6B? zT`uIL0!DO`rG?=vQc6>riYP3VDd8!YQr>7vZj$NnY76Mr6#S|5S$#Sad$D#arR{`i zin%W$5EoksIYMo}c`w8fKEpg6`g6Z+JXWz^L_dn-r?k{b5eB3XKu*=Z{2Hx=W$Tf> zVvkV*kNUTV!jjGNAu^Rb0g_B4fdKMU#I3pyYZRzij1Jb&+CVhvLpQqbP80J%yZ()d zZ}QnSCC(!L=B3G05!^R(8a@Kfq0~z*ztzgWEV;2uef;#m%DQVQyZv`p*9u8YvRu}N z*LDy>YG7R_=X8IqLZ6#JxfDiuhhdlZFM4J&sTSA#!!*%8$XEgf=$j#iDdld5UE`Xh z^A^+Q@vSdJzY6}L`Ouj+eL70!9++4rlq!U9)%^CW@hZqY3Ug>tZsNrGasVuE8*eMf zC@p@O7B@l6AoqS=v_JfAyNS6?uz?SqMbt+o$F#UFZ_ef@+Y5X@s-nr$GehsU4T<30G7FjP!%kzZ`umxHZ>^Qu78%~a#9Nph3<%)t!kk*25F&V$wfLCkR4@#r^m0i+IS@hy5_ayPdV zNt3Zl`~;~8ba0xZ^9DVJ1Iy*p(7 z*Xeh;=8!I1FlY;|EXLW<5qoe?3!}qXBi>3AGb%s`QP%ivM;1&0zp1B?-reb{5#6Kd zDRJ**6_GJ8oi#Q0+xh!5R*0{gZI2F-SKRowNvhXs(&Kg6@IF5`TSfON3~K%k-K>r1 zPwAlJPd^#fCZ3gCZ^{Faw)2Rs-iwnzkN5eMS1js*DA7P5V^Iyc|G8v5h3^-}ze)?Z z*|CsTOt1*varRt$zp50p78u|?fh1dm#BMFZiEDIh6z_%i*M4S9V14W882R@47MAGe z_u);|Qi`-Gf(>PN@}@~OhlioZJ%R<#CQ^fi8~qkPrJY}=<@7|S>xr&gns0=&oP&pD X%hVz6s@WHxK!C2Mp+=25A@Y9!nF9~R literal 0 HcmV?d00001 diff --git a/generators/client/templates/playwright/integration/account/change-password.spec.js.ejs b/generators/client/templates/playwright/integration/account/change-password.spec.js.ejs new file mode 100644 index 000000000..38bf8daa5 --- /dev/null +++ b/generators/client/templates/playwright/integration/account/change-password.spec.js.ejs @@ -0,0 +1,93 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../../utils/test-utils' + +test.describe('Change user password', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/account/password') + }) + + test('should greet with Change password title', async ({ page }) => { + await expect(page.getByTestId('passwordTitle')).toHaveText('Change password') + }) + + test('should require mandatory fields to be filled', async ({ page }) => { + const updateButton = page.getByTestId('passwordForm').getByText('Update password') + await expect(updateButton).toBeDisabled() + }) + + test('should require current password', async ({ page }) => { + const form = page.getByTestId('passwordForm') + const currentPassword = form.getByRole('textbox', { name: 'current password' }) + + await currentPassword.fill('admin') + await currentPassword.clear() + await currentPassword.blur() + + const error = form.getByTestId('currentPassword-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Password is mandatory') + }) + + test('should require new password', async ({ page }) => { + const form = page.getByTestId('passwordForm') + const newPassword = form.getByRole('textbox', { name: /^new password/i }) + + await newPassword.fill('admin') + await newPassword.clear() + await newPassword.blur() + + const error = form.getByTestId('newPassword-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Password is mandatory') + }) + + test('should require to confirm new password', async ({ page }) => { + const form = page.getByTestId('passwordForm') + const confirmPassword = form.getByRole('textbox', { name: 'confirm new password' }) + + await confirmPassword.fill('admin') + await confirmPassword.clear() + await confirmPassword.blur() + + const error = form.getByTestId('newPasswordConfirm-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Password is mandatory') + }) + + test('should require new and confirm passwords to match', async ({ page }) => { + const form = page.getByTestId('passwordForm') + + await form.getByRole('textbox', { name: /^new password/i }).fill('abcd') + await form.getByRole('textbox', { name: /^new password/i }).blur() + + await form.getByRole('textbox', { name: 'confirm new password' }).fill('defg') + await form.getByRole('textbox', { name: 'confirm new password' }).blur() + + const error = form.getByTestId('newPasswordConfirm-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Password and its confirmation do not match') + }) + + test('should update user password', async ({ page }) => { + const form = page.getByTestId('passwordForm') + + await form.getByRole('textbox', { name: 'current password' }).fill('admin') + await form.getByRole('textbox', { name: 'current password' }).blur() + + await form.getByRole('textbox', { name: /^new password/i }).fill('admin') + await form.getByRole('textbox', { name: /^new password/i }).blur() + + await form.getByRole('textbox', { name: 'confirm new password' }).fill('admin') + await form.getByRole('textbox', { name: 'confirm new password' }).blur() + + const updateButton = form.getByText('Update password') + await expect(updateButton).toBeEnabled() + await updateButton.click() + + const successMsg = page.getByTestId('successMsg') + await expect(successMsg).toContainText('Password changed!') + }) +}) diff --git a/generators/client/templates/playwright/integration/account/register.spec.js.ejs b/generators/client/templates/playwright/integration/account/register.spec.js.ejs new file mode 100644 index 000000000..9b7f5c4f0 --- /dev/null +++ b/generators/client/templates/playwright/integration/account/register.spec.js.ejs @@ -0,0 +1,131 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../../utils/test-utils' + +test.describe('Register User', () => { + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/account/register') + }) + + test('should greet with Create user title', async ({ page }) => { + const title = page.getByTestId('registerTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Create user account') + }) + + test('should require mandatory fields', async ({ page }) => { + const createButton = page.getByTestId('registerForm').getByText('Create account') + await expect(createButton).toBeDisabled() + }) + + test('should require username', async ({ page }) => { + const form = page.getByTestId('registerForm') + const username = form.getByRole('textbox', { name: 'username' }) + + await username.fill('admin') + await username.clear() + await username.blur() + + const error = form.getByTestId('username-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Username is mandatory') + }) + + test('should require email', async ({ page }) => { + const form = page.getByTestId('registerForm') + const email = form.getByRole('textbox', { name: 'email' }) + + await email.fill('admin@localhost.org') + await email.clear() + await email.blur() + + const error = form.getByTestId('email-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Email is mandatory') + }) + + test('should require valid email', async ({ page }) => { + const form = page.getByTestId('registerForm') + const email = form.getByRole('textbox', { name: 'email' }) + + await email.fill('admin@localhost') + await email.blur() + + const error = form.getByTestId('email-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Email address is not valid') + }) + + test('should require password', async ({ page }) => { + const form = page.getByTestId('registerForm') + const password = form.getByRole('textbox', { name: /^password/i }) + + await password.fill('password') + await password.clear() + await password.blur() + + const error = form.getByTestId('password-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Password is mandatory') + }) + + test('should require confirm password', async ({ page }) => { + const form = page.getByTestId('registerForm') + const confirmPassword = form.getByRole('textbox', { name: 'confirm password' }) + + await confirmPassword.fill('password') + await confirmPassword.clear() + await confirmPassword.blur() + + const error = form.getByTestId('passwordConfirm-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Password is mandatory') + }) + + test('should require password and confirm password to match', async ({ page }) => { + const form = page.getByTestId('registerForm') + + await form.getByRole('textbox', { name: /^password/i }).fill('abcd') + await form.getByRole('textbox', { name: 'confirm password' }).fill('defg') + + const error = form.getByTestId('passwordConfirm-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Password and its confirmation do not match') + }) + + test('should not allow user account creation with duplicate username', async ({ page }) => { + const form = page.getByTestId('registerForm') + + await form.getByRole('textbox', { name: 'username' }).fill('admin') + await form.getByRole('textbox', { name: 'email' }).fill('joe@localhost.org') + await form.getByRole('textbox', { name: /^password/i }).fill('jondoe') + await form.getByRole('textbox', { name: 'confirm password' }).fill('jondoe') + + const createButton = page.getByTestId('registerForm').getByText('Create account') + await expect(createButton).toBeEnabled() + await createButton.click() + + const errorMsg = page.getByTestId('errorMsg') + await expect(errorMsg).toContainText('Login name already in use. Please choose another one.') + }) + + test('should create new user account', async ({ page }) => { + const form = page.getByTestId('registerForm') + + await form.getByRole('textbox', { name: 'username' }).fill('jon') + await form.getByRole('textbox', { name: 'email' }).fill('joe@localhost.org') + await form.getByRole('textbox', { name: /^password/i }).fill('jondoe') + await form.getByRole('textbox', { name: 'confirm password' }).fill('jondoe') + + const createButton = page.getByTestId('registerForm').getByText('Create account') + await expect(createButton).toBeEnabled() + await createButton.click() + + const successMsg = page.getByTestId('successMsg') + await expect(successMsg).toContainText( + 'User account successfully created. Please check your email for confirmation.', + ) + + await expect(page.getByTestId('registerForm')).toBeHidden() + }) +}) diff --git a/generators/client/templates/playwright/integration/account/reset/init-password.spec.js.ejs b/generators/client/templates/playwright/integration/account/reset/init-password.spec.js.ejs new file mode 100644 index 000000000..037c9be9b --- /dev/null +++ b/generators/client/templates/playwright/integration/account/reset/init-password.spec.js.ejs @@ -0,0 +1,55 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../../../utils/test-utils' + +test.describe('Forgot password', () => { + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/account/reset/init') + }) + + test('should greet with forgot password title', async ({ page }) => { + const title = page.getByTestId('forgotPwdTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Reset your password') + }) + + test('should verify instructions to reset password', async ({ page }) => { + const warningMsg = page.getByTestId('warningMsg') + await expect(warningMsg).toBeVisible() + await expect(warningMsg).toContainText("Enter your user account's verified email address.") + }) + + test('should require mandatory fields', async ({ page }) => { + const submitButton = page.getByTestId('forgotPwdForm').getByText('Send password reset email') + await expect(submitButton).toBeDisabled() + }) + + test('should require email', async ({ page }) => { + const form = page.getByTestId('forgotPwdForm') + + const emailInput = form.getByRole('textbox', { name: 'email' }) + await emailInput.fill('admin') + await emailInput.clear() + await emailInput.blur() + + const error = form.getByTestId('email-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Email is mandatory') + }) + + test('should reset user password', async ({ page }) => { + const form = page.getByTestId('forgotPwdForm') + + const emailInput = form.getByRole('textbox', { name: 'email' }) + await emailInput.fill('admin@localhost.org') + await emailInput.press('Enter') + + const successMsg = page.getByTestId('successMsg') + await expect(successMsg).toBeVisible() + await expect(successMsg).toContainText( + 'Check your email for details on how to reset your password.', + ) + + await expect(form).toBeHidden() + }) +}) diff --git a/generators/client/templates/playwright/integration/account/settings.spec.js.ejs b/generators/client/templates/playwright/integration/account/settings.spec.js.ejs new file mode 100644 index 000000000..7a646cd62 --- /dev/null +++ b/generators/client/templates/playwright/integration/account/settings.spec.js.ejs @@ -0,0 +1,91 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../../utils/test-utils' + +test.describe('User Settings', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/account/settings') + }) + + test('should greet with User Settings title', async ({ page }) => { + await expect(page.getByTestId('settingsTitle')).toHaveText( + `User settings for [${process.env.ADMIN_USERNAME}]`, + ) + }) + + test('should display current settings', async ({ page }) => { + const form = page.getByTestId('settingsForm') + + const firstName = form.getByRole('textbox', { name: 'first name' }) + await expect(firstName).toHaveValue(<%_ if (databaseTypeSql) { _%>'Admin'<%_ } else { _%>'admin'<%_ }_%>) + + const lastName = form.getByRole('textbox', { name: 'last name' }) + await expect(lastName).toHaveValue('Admin') + + const email = form.getByRole('textbox', { name: 'email' }) + await expect(email).toHaveValue('admin@localhost.org') + }) + + test('should display form control validation messages', async ({ page }) => { + const form = page.getByTestId('settingsForm') + + const firstName = form.getByRole('textbox', { name: 'first name' }) + await firstName.fill('AveryLongFirstNameThatExceedsTheMaximumLengthLimitToCheckValidation') + await firstName.blur() + const firstNameError = form.getByTestId('firstName-error') + await expect(firstNameError).toBeVisible() + await expect(firstNameError).toContainText('First name cannot be longer than 50 characters') + + const lastName = form.getByRole('textbox', { name: 'last name' }) + await lastName.fill('AveryLongLastNameThatExceedsTheMaximumLengthLimitToCheckValidation') + await lastName.blur() + const lastNameError = form.getByTestId('lastName-error') + await expect(lastNameError).toBeVisible() + await expect(lastNameError).toContainText('Last name cannot be longer than 50 characters') + + const email = form.getByRole('textbox', { name: 'email' }) + await email.clear() + await email.blur() + const emailRequiredError = form.getByTestId('email-error') + await expect(emailRequiredError).toBeVisible() + await expect(emailRequiredError).toContainText('Email is mandatory') + + const email2 = form.getByRole('textbox', { name: 'email' }) + await email2.fill('admin@localhost') + await email2.blur() + const emailFormatError = form.getByTestId('email-error') + await expect(emailFormatError).toBeVisible() + await expect(emailFormatError).toContainText('Email address is not valid') + + const updateButton = form.getByText('Update Settings') + await expect(updateButton).toBeDisabled() + }) + + test('should update user settings', async ({ page }) => { + const form = page.getByTestId('settingsForm') + + const firstName = form.getByRole('textbox', { name: 'first name' }) + await firstName.clear() + await firstName.fill(<%_ if (databaseTypeSql) { _%>'Admin'<%_ } else { _%>'admin'<%_ }_%>) + await firstName.blur() + + const lastName = form.getByRole('textbox', { name: 'last name' }) + await lastName.clear() + await lastName.fill('Admin') + await lastName.blur() + + const email = form.getByRole('textbox', { name: 'email' }) + await email.clear() + await email.fill('admin@localhost.org') + await email.blur() + + const updateButton = form.getByText('Update Settings') + await expect(updateButton).toBeEnabled() + await updateButton.click() + + const successMsg = page.getByTestId('successMsg') + await expect(successMsg).toContainText('Settings changed!') + }) +}) diff --git a/generators/client/templates/playwright/integration/admin/gateway.spec.js.ejs b/generators/client/templates/playwright/integration/admin/gateway.spec.js.ejs new file mode 100644 index 000000000..d1e90f5f0 --- /dev/null +++ b/generators/client/templates/playwright/integration/admin/gateway.spec.js.ejs @@ -0,0 +1,30 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../../utils/test-utils' + +test.describe('Gateway page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/admin/gateway') + }) + + test('should greet with Gateway page title', async ({ page }) => { + const title = page.getByTestId('gatewayTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Gateway routes') + }) + + test('should display Gateway routes table', async ({ page }) => { + const table = page.getByTestId('gatewayTable') + await expect(table).toBeVisible() + + const headerRow = table.getByRole('row').first() + const headers = headerRow.getByRole('columnheader') + + await expect(headers).toHaveCount(3) + await expect(headers.nth(0)).toContainText('Service') + await expect(headers.nth(1)).toContainText('Route') + await expect(headers.nth(2)).toContainText('Service Instances') + }) +}) diff --git a/generators/client/templates/playwright/integration/admin/logger.spec.js.ejs b/generators/client/templates/playwright/integration/admin/logger.spec.js.ejs new file mode 100644 index 000000000..39bb0ed00 --- /dev/null +++ b/generators/client/templates/playwright/integration/admin/logger.spec.js.ejs @@ -0,0 +1,151 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../../utils/test-utils' + +test.describe('Loggers page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/admin/logger') + }) + + test('should greet with loggers page title and filter control', async ({ page }) => { + await expect(page.getByTestId('loggersTitle')).toBeVisible() + await expect(page.getByTestId('loggersTitle')).toContainText('Loggers') + + const loggerInput = page + .getByTestId('loggerSearchForm') + .getByRole('searchbox', { name: 'Filter logger' }) + await expect(loggerInput).toHaveValue('') + }) + + test('should display loggers table', async ({ page }) => { + const table = page.getByTestId('loggersTable') + await expect(table).toBeVisible() + + const firstRow = table.locator('tr').first() + const headers = firstRow.locator('th') + await expect(headers).toHaveCount(1) + await expect(headers.first()).toContainText('Name') + }) + + test('should display logger name and the default enabled logger level in the table', async ({ + page, + }) => { + await page + .getByTestId('loggerSearchForm') + .getByRole('searchbox', { name: 'Filter logger' }) + .fill(<%_ if (databaseTypeSql) { _%>'org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator'<%_ } else { _%>'org.mongodb.driver'<%_ }_%>) + + const row = page + .getByTestId('loggersTable') + .getByRole('row') + .filter({ hasText: <%_ if (databaseTypeSql) { _%>'org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator'<%_ } else { _%>'org.mongodb.driver'<%_ }_%> }) + .first() + + const buttons = row.locator('td').first().locator('button') + await expect(buttons).toHaveCount(6) + + await expect(row.getByTestId('offLogLevelBtn')).toBeHidden() + await expect(row.getByTestId('offLogLevelBtn')).toBeEnabled() + await expect(row.getByTestId('errorLogLevelBtn')).toBeHidden() + await expect(row.getByTestId('errorLogLevelBtn')).toBeEnabled() + await expect(row.getByTestId('warnLogLevelBtn')).toBeVisible() + await expect(row.getByTestId('warnLogLevelBtn')).toBeDisabled() + await expect(row.getByTestId('infoLogLevelBtn')).toBeHidden() + await expect(row.getByTestId('infoLogLevelBtn')).toBeEnabled() + await expect(row.getByTestId('debugLogLevelBtn')).toBeHidden() + await expect(row.getByTestId('debugLogLevelBtn')).toBeEnabled() + await expect(row.getByTestId('traceLogLevelBtn')).toBeHidden() + await expect(row.getByTestId('traceLogLevelBtn')).toBeEnabled() + }) + + test('should display actions available on the current selected logger', async ({ page }) => { + await page + .getByTestId('loggerSearchForm') + .getByRole('searchbox', { name: 'Filter logger' }) + .fill(<%_ if (databaseTypeSql) { _%>'org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator'<%_ } else { _%>'org.mongodb.driver'<%_ }_%>) + + const row = page + .getByTestId('loggersTable') + .getByRole('row') + .filter({ hasText: <%_ if (databaseTypeSql) { _%>'org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator'<%_ } else { _%>'org.mongodb.driver'<%_ }_%> }) + .first() + + await row.hover() + + const buttons = row.locator('td').first().locator('button') + await expect(buttons).toHaveCount(6) + + await expect(row.getByTestId('offLogLevelBtn')).toBeVisible() + await expect(row.getByTestId('offLogLevelBtn')).toBeEnabled() + await expect(row.getByTestId('errorLogLevelBtn')).toBeVisible() + await expect(row.getByTestId('errorLogLevelBtn')).toBeEnabled() + await expect(row.getByTestId('warnLogLevelBtn')).toBeVisible() + await expect(row.getByTestId('warnLogLevelBtn')).toBeDisabled() + await expect(row.getByTestId('infoLogLevelBtn')).toBeVisible() + await expect(row.getByTestId('infoLogLevelBtn')).toBeEnabled() + await expect(row.getByTestId('debugLogLevelBtn')).toBeVisible() + await expect(row.getByTestId('debugLogLevelBtn')).toBeEnabled() + await expect(row.getByTestId('traceLogLevelBtn')).toBeVisible() + await expect(row.getByTestId('traceLogLevelBtn')).toBeEnabled() + }) + + test('should change logger level from WARN -> INFO, INFO -> WARN', async ({ page }) => { + await page + .getByTestId('loggerSearchForm') + .getByRole('searchbox', { name: 'Filter logger' }) + .fill(<%_ if (databaseTypeSql) { _%>'org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator'<%_ } else { _%>'org.mongodb.driver'<%_ }_%>) + + const row = page + .getByTestId('loggersTable') + .getByRole('row') + .filter({ hasText: <%_ if (databaseTypeSql) { _%>'org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator'<%_ } else { _%>'org.mongodb.driver'<%_ }_%> }) + .first() + + await row.hover() + + const buttons = row.locator('td').first().locator('button') + await expect(buttons).toHaveCount(6) + + await row.getByTestId('infoLogLevelBtn').click() + await expect(row.getByTestId('infoLogLevelBtn')).toBeVisible() + await expect(row.getByTestId('infoLogLevelBtn')).toBeDisabled() + + await row.getByTestId('warnLogLevelBtn').click() + }) + + test('should filter loggers by name', async ({ page }) => { + const table = page.getByTestId('loggersTable') + await table.locator('tbody > tr').nth(1).waitFor() + const rowsBeforeFilter = await table.locator('tbody > tr').count() + + await page + .getByTestId('loggerSearchForm') + .getByRole('searchbox', { name: 'Filter logger' }) + .fill('ROOT') + + const rowsAfterFilter = await table.locator('tbody > tr').count() + expect(rowsAfterFilter).not.toBe(rowsBeforeFilter) + + await page + .getByTestId('loggerSearchForm') + .getByRole('searchbox', { name: 'Filter logger' }) + .clear() + + const rowsAfterClear = await table.locator('tbody > tr').count() + expect(rowsAfterClear).toBe(rowsBeforeFilter) + }) + + test('should validate the pagination controls', async ({ page }) => { + const pageControl = page.getByTestId('pageCtrl').first() + await expect(pageControl).toContainText(/1-\d+ of \d+/) + + const prevButton = pageControl.getByTestId('prevPageCtrl') + await expect(prevButton).toBeDisabled() + await expect(pageControl).toContainText('1') + + const nextButton = pageControl.getByTestId('nextPageCtrl') + await expect(nextButton).toBeEnabled() + }) +}) diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-create.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-create.spec.js.ejs new file mode 100644 index 000000000..1ee45af69 --- /dev/null +++ b/generators/client/templates/playwright/integration/admin/user-management/user-create.spec.js.ejs @@ -0,0 +1,112 @@ +import { expect, test } from '@playwright/test' +import { deleteRequest, unregisterServiceWorkers } from '../../../utils/test-utils' + +test.describe('Create user page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto(`/admin/user-management/new`) + }) + + test('should greet with Create user title', async ({ page }) => { + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Create user account') + }) + + test('should require mandatory fields', async ({ page }) => { + const createButton = page.getByTestId('addUserForm').getByText('Create user account') + await expect(createButton).toBeDisabled() + }) + + test('should require username', async ({ page }) => { + const form = page.getByTestId('addUserForm') + const username = form.getByRole('textbox', { name: 'username' }) + + await username.fill('admin') + await username.clear() + await username.blur() + + const error = form.getByTestId('username-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Username is mandatory') + }) + + test('should require email', async ({ page }) => { + const form = page.getByTestId('addUserForm') + const email = form.getByRole('textbox', { name: 'email' }) + + await email.fill('admin@localhost.org') + await email.clear() + await email.blur() + + const error = form.getByTestId('email-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Email is mandatory') + }) + + test('should require roles', async ({ page }) => { + const form = page.getByTestId('addUserForm') + + await form.getByRole('textbox', { name: 'roles' }).click() + const rolesOptions = page.getByTestId('roles-options') + const firstCheckbox = rolesOptions.getByRole('checkbox').first() + + await firstCheckbox.check() + await firstCheckbox.uncheck() + await page.getByTestId('roles-bg').click() + + const error = form.getByTestId('roles-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Select at least one role') + }) + + test('should navigate back to the user list page', async ({ page }) => { + await page.getByRole('button', { name: 'cancel' }).click() + + await expect(page).toHaveURL('/admin/user-management') + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Users') + }) + + test.describe('create account', () => { + let randomUser + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + randomUser = `test${new Date().getTime()}` + await page.goto(`/admin/user-management/new`) + }) + + test.afterEach(async ({ page }) => { + await deleteRequest(page, `api/admin/users/${randomUser}`) + }) + + test('should create a new user account', async ({ page }) => { + const form = page.getByTestId('addUserForm') + + await form.getByRole('textbox', { name: 'username' }).fill(randomUser) + await form.getByRole('textbox', { name: 'first name' }).fill('john') + await form.getByRole('textbox', { name: 'last name' }).fill('doe') + await form.getByRole('textbox', { name: 'email' }).fill(`${randomUser}@localhost.org`) + + await form.getByRole('textbox', { name: 'roles' }).click() + await page.getByTestId('roles-options').getByRole('checkbox').first().check() + await page.getByTestId('roles-bg').click() + + const createButton = form.getByText('Create user account') + await expect(createButton).toBeEnabled() + await createButton.click() + + const toast = page.getByTestId('toast-success') + await expect(toast).toContainText('A user is created with identifier') + + await expect(page).toHaveURL('/admin/user-management') + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Users') + }) + }) +}) diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs new file mode 100644 index 000000000..7aa7ccffb --- /dev/null +++ b/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs @@ -0,0 +1,68 @@ +import { expect, test } from '@playwright/test' +import { deleteRequest, save, unregisterServiceWorkers } from '../../../utils/test-utils' + +test.describe('User delete dialog page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + let randomUser + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + randomUser = `test${new Date().getTime()}` + + await save(page, 'api/admin/users', { + login: randomUser, + firstName: '', + lastName: '', + email: `${randomUser}@localhost.org`, + activated: true, + authorities: ['ROLE_USER'], + }) + + await page.goto('/admin/user-management') + + const row = page.getByTestId('userMgmtTable').getByRole('row').filter({ hasText: randomUser }) + + await row.hover() + await row.getByRole('button', { name: 'delete' }).click() + }) + + test.afterEach(async ({ page }) => { + await deleteRequest(page, `api/admin/users/${randomUser}`) + }) + + test('should display delete user dialog', async ({ page }) => { + const modal = page.getByTestId('svlModal') + const heading = modal.getByRole('heading', { level: 1 }) + + await expect(heading).toBeVisible() + await expect(heading).toContainText('Confirm delete operation') + + const text = modal.getByText('Are you sure you want to delete the user?') + await expect(text).toBeVisible() + + await expect(modal.getByRole('button', { name: 'delete' })).toBeEnabled() + await expect(modal.getByRole('button', { name: 'cancel' })).toBeEnabled() + }) + + test('should close the dialog without deleting user', async ({ page }) => { + const modal = page.getByTestId('svlModal') + await modal.getByRole('button', { name: 'cancel' }).click() + + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Users') + }) + + test('should delete the user', async ({ page }) => { + const modal = page.getByTestId('svlModal') + await modal.getByRole('button', { name: 'delete' }).click() + + const toast = page.getByTestId('toast-success') + await expect(toast).toContainText('A user is deleted with identifier') + + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Users') + }) +}) diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-list.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-list.spec.js.ejs new file mode 100644 index 000000000..8b9d02dfe --- /dev/null +++ b/generators/client/templates/playwright/integration/admin/user-management/user-list.spec.js.ejs @@ -0,0 +1,101 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../../../utils/test-utils' + +test.describe('User Management list page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/admin/user-management') + }) + + test('should greet with users page title', async ({ page }) => { + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Users') + }) + + test('should display users table', async ({ page }) => { + const table = page.getByTestId('userMgmtTable') + await expect(table).toBeVisible() + + const headerRow = table.getByRole('row').first() + const headers = await headerRow.getByRole('columnheader').all() + + expect(headers).toHaveLength(7) + await expect(headerRow.getByRole('columnheader').nth(0)).toContainText('ID') + await expect(headerRow.getByRole('columnheader').nth(1)).toContainText('Login') + await expect(headerRow.getByRole('columnheader').nth(2)).toContainText('Email') + await expect(headerRow.getByRole('columnheader').nth(3)).toContainText('Roles') + await expect(headerRow.getByRole('columnheader').nth(4)).toContainText('Created At') + await expect(headerRow.getByRole('columnheader').nth(5)).toContainText('Modified By') + await expect(headerRow.getByRole('columnheader').nth(6)).toContainText('Modified At') + }) + + test('should display "user" user record in the table', async ({ page }) => { + const userRow = page + .getByTestId('userMgmtTable') + .getByRole('row') + .filter({ hasText: 'user@localhost' }) + + const cells = userRow.getByRole('cell') + await expect(cells.nth(1)).toContainText('user') + await expect(cells.nth(2)).toContainText('user@localhost') + await expect(cells.nth(3)).toContainText('ROLE_USER') + await expect(cells.nth(5)).toContainText('system') + }) + + test('should not allow actions on the current logged in user', async ({ page }) => { + const adminRow = page + .getByTestId('userMgmtTable') + .getByRole('row') + .filter({ hasText: 'admin@localhost' }) + + // eslint-disable-next-line playwright/no-force-option + await adminRow.hover({ force: true }) + + const actions = adminRow.getByRole('cell').last() + await expect(actions.getByRole('button', { name: 'toggleActivation' })).toBeDisabled() + await expect(actions.getByRole('button', { name: 'view' })).toBeEnabled() + await expect(actions.getByRole('button', { name: 'edit' })).toBeDisabled() + await expect(actions.getByRole('button', { name: 'delete' })).toBeDisabled() + }) + + test('should allow deactivation of "user" account record', async ({ page }) => { + const userRow = page + .getByTestId('userMgmtTable') + .getByRole('row') + .filter({ hasText: 'user@localhost' }) + + await userRow.hover() + + const toggleButton = userRow + .getByRole('cell') + .last() + .getByRole('button', { name: 'toggleActivation' }) + + await expect(toggleButton).toBeEnabled() + }) + + test('should validate the pagination controls', async ({ page }) => { + const pageControl = page.getByTestId('pageCtrl').first() + await expect(pageControl).toContainText(/1-\d+ of \d+/) + + const pagination = pageControl.locator('div').nth(1) + await expect(pagination.getByTestId('prevPageCtrl')).toBeDisabled() + await expect(pagination.locator('div')).toHaveText('1') + await expect(pagination.getByTestId('nextPageCtrl')).toBeDisabled() + }) + + test('should sort the records by login field', async ({ page }) => { + const table = page.getByTestId('userMgmtTable') + const firstRow = table.getByRole('row').nth(1) + const loginCell = firstRow.getByRole('cell').nth(1) + const loginValueBefore = await loginCell.textContent() + + const loginHeader = table.getByRole('row').first().getByRole('columnheader').nth(1) + await loginHeader.getByRole('button').click() + + await expect(loginCell).not.toHaveText(loginValueBefore) + }) +}) diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs new file mode 100644 index 000000000..80aa6eca8 --- /dev/null +++ b/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs @@ -0,0 +1,124 @@ +import { expect, test } from '@playwright/test' +import { deleteRequest, save, unregisterServiceWorkers } from '../../../utils/test-utils' + +test.describe('Update user page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + let randomUser + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + randomUser = `test${new Date().getTime()}` + const response = await save(page, 'api/admin/users', { + login: randomUser, + firstName: 'John', + lastName: 'Doe', + email: `${randomUser}@localhost.org`, + activated: true, + authorities: ['ROLE_USER'], + }) + await page.goto(`/admin/user-management/${response.login}/edit`) + }) + + test.afterEach(async ({ page }) => { + await deleteRequest(page, `api/admin/users/${randomUser}`) + }) + + test('should greet with update user title', async ({ page }) => { + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Update user account') + }) + + test('should be populated and have valid state', async ({ page }) => { + const form = page.getByTestId('addUserForm') + + await expect(form.getByRole('textbox', { name: 'username' })).toHaveValue(randomUser) + await expect(form.getByRole('textbox', { name: 'first name' })).toHaveValue('John') + await expect(form.getByRole('textbox', { name: 'last name' })).toHaveValue('Doe') + await expect(form.getByRole('textbox', { name: 'email' })).toHaveValue( + `${randomUser}@localhost.org`, + ) + await expect(form.getByRole('checkbox', { name: 'activate' })).toBeChecked() + await expect(form.getByRole('textbox', { name: 'roles' })).toHaveValue('ROLE_USER') + + await expect(form.getByText('Update user account')).toBeEnabled() + }) + + test('should require username', async ({ page }) => { + const form = page.getByTestId('addUserForm') + const username = form.getByRole('textbox', { name: 'username' }) + + await username.clear() + await username.blur() + + const error = form.getByTestId('username-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Username is mandatory') + }) + + test('should require email', async ({ page }) => { + const form = page.getByTestId('addUserForm') + const email = form.getByRole('textbox', { name: 'email' }) + + await email.clear() + await email.blur() + + const error = form.getByTestId('email-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Email is mandatory') + }) + + test('should require roles', async ({ page }) => { + const form = page.getByTestId('addUserForm') + + await form.getByRole('textbox', { name: 'roles' }).click() + const rolesOptions = page.getByTestId('roles-options') + + await rolesOptions.getByRole('checkbox').first().uncheck() + await rolesOptions.getByRole('checkbox').nth(1).uncheck() + await page.getByTestId('roles-bg').click() + + const error = form.getByTestId('roles-error') + await expect(error).toBeVisible() + await expect(error).toContainText('Select at least one role') + }) + + test('should navigate back to the user list page', async ({ page }) => { + await page.getByRole('button', { name: 'cancel' }).click() + + await expect(page).toHaveURL('/admin/user-management') + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Users') + }) + + test('should update user account details', async ({ page }) => { + const form = page.getByTestId('addUserForm') + + await form.getByRole('textbox', { name: 'first name' }).fill('update') + await form.getByRole('textbox', { name: 'last name' }).fill('update') + + const email = form.getByRole('textbox', { name: 'email' }) + await email.clear() + await email.fill(`${randomUser}-update@localhost.org`) + + await form.getByRole('textbox', { name: 'roles' }).click() + const rolesOptions = page.getByTestId('roles-options') + await rolesOptions.getByRole('checkbox').first().check() + await rolesOptions.getByRole('checkbox').nth(1).check() + await page.getByTestId('roles-bg').click() + + const updateButton = form.getByText('Update user account') + await expect(updateButton).toBeEnabled() + await updateButton.click() + + const toast = page.getByTestId('toast-success') + await expect(toast).toContainText('A user is updated with identifier') + + await expect(page).toHaveURL('/admin/user-management') + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Users') + }) +}) diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs new file mode 100644 index 000000000..fef4ed96a --- /dev/null +++ b/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs @@ -0,0 +1,52 @@ +import { expect, test } from '@playwright/test' +import { deleteRequest, save, unregisterServiceWorkers } from '../../../utils/test-utils' + +test.describe('User view details page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + let randomUser + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + randomUser = `test${new Date().getTime()}` + const response = await save(page, 'api/admin/users', { + login: randomUser, + firstName: 'John', + lastName: 'Doe', + email: `${randomUser}@localhost.org`, + activated: true, + authorities: ['ROLE_USER'], + }) + await page.goto(`/admin/user-management/${response.login}/view`) + }) + + test.afterEach(async ({ page }) => { + await deleteRequest(page, `api/admin/users/${randomUser}`) + }) + + test('should display user account details', async ({ page }) => { + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText(`User Details [ ${randomUser} ]`) + + const table = page.locator('div.table') + const cells = table.locator('.table-cell') + await expect(cells).toHaveCount(18) + + await expect(cells.nth(1)).toContainText(randomUser) + await expect(cells.nth(3)).toContainText('John') + await expect(cells.nth(5)).toContainText('Doe') + await expect(cells.nth(7)).toContainText(`${randomUser}@localhost.org`) + await expect(cells.nth(9)).toContainText('admin') + await expect(cells.nth(13)).toContainText('admin') + await expect(cells.nth(17)).toContainText('ROLE_USER') + }) + + test('should navigate back to the user list page', async ({ page }) => { + await page.getByRole('button', { name: 'back' }).click() + + const title = page.getByTestId('userMgmtTitle') + await expect(title).toBeVisible() + await expect(title).toContainText('Users') + }) +}) diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs new file mode 100644 index 000000000..3da729224 --- /dev/null +++ b/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs @@ -0,0 +1,116 @@ +<%_ + const entityFakeData = generateFakeData('cypress'); + const containsBinaryField = fields.find(field => (field.fieldTypeBinary && !field.blobContentTypeText)); + const containsMandatoryField = fields.find(field => field.fieldValidationRequired); + const containsFieldValidation = containsMandatoryField || fields.find(field => (field.fieldValidationMinLength + || field.fieldValidationMaxLength + || field.fieldValidationMin + || field.fieldValidationMax + || (!field.fieldValidationRequired && field.fieldTypeNumeric))); + const placeHolderText = 'lorem ipsum'; + const getMinLengthText = function(len) { + return placeHolderText.substr(0, len > 1 ? len-1 : 0); + } + const getMaxLengthText = function(len) { + let maxLengthText = placeHolderText; + while(maxLengthText.length < len) { + maxLengthText+= placeHolderText; + } + return maxLengthText; + } +_%> +import { expect, test } from '@playwright/test' +import { getByName, unregisterServiceWorkers } from '../../../<%= clientRootFolder !== '' ? '../' : '' %>utils/test-utils' + +test.describe('Create <%= entityInstance %> page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/entities/<%= entityFolderName %>/new') + }) + + test('should greet with Create <%= entityInstance %> title', async ({ page }) => { + const title = page.getByTestId('<%= entityInstance %>Title') + await expect(title).toBeVisible() + await expect(title).toContainText('Create <%= entityClassHumanized %>') + }) + +<%_ if (containsMandatoryField) { _%> + test('should require mandatory fields', async ({ page }) => { + const createButton = page.getByTestId('add<%= entityAngularName %>Form').getByText('Create <%= entityClassHumanized %>') + await expect(createButton).toBeDisabled() + }) +<%_ } _%> + +<%_ if (containsFieldValidation) { _%> + test('should validate field values', async ({ page }) => { + const form = page.getByTestId('add<%= entityAngularName %>Form') +<%_ + for (field of fields.filter(field => !field.id)) { + const fieldValue = !entityFakeData ? field.generateFakeData('cypress') : entityFakeData[field.fieldName]; + if (fieldValue === undefined) { + warning(`Error generating a value for field ${field.fieldName}`); + } +_%> + const <%= field.fieldName %> = getByName(form, '<%= field.fieldName %>') + const <%= field.fieldName %>Error = form.getByTestId('<%= field.fieldName %>-error') + +<%_ if (field.fieldValidationRequired) { _%> + await <%= field.fieldName %>.fill('<%= fieldValue %>') + await <%= field.fieldName %>.clear() + await <%= field.fieldName %>.blur() + + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> is mandatory') +<%_ } _%> + +<%_ if (field.fieldValidationMinLength) { _%> + await <%= field.fieldName %>.fill('<%= getMinLengthText(field.fieldValidateRulesMinlength) %>') + await <%= field.fieldName %>.blur() + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> should be greater than <%= field.fieldValidateRulesMinlength %> characters') +<%_ } _%> + +<%_ if (field.fieldValidationMaxLength) { _%> + await <%= field.fieldName %>.fill('<%= getMaxLengthText(field.fieldValidateRulesMaxlength) %>') + await <%= field.fieldName %>.blur() + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> cannot be longer than <%= field.fieldValidateRulesMaxlength %> characters') +<%_ } _%> + +<%_ if (field.fieldTypeNumeric && !field.fieldValidationRequired) { _%> + await <%= field.fieldName %>.fill('1') + await <%= field.fieldName %>.clear() + await <%= field.fieldName %>.fill('-') + await <%= field.fieldName %>.blur() + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> should be numeric') +<%_ } _%> + +<%_ if (field.fieldTypeNumeric && field.fieldValidationMin) { _%> + await <%= field.fieldName %>.fill('<%= Number(field.fieldValidateRulesMin) - 1 %>') + await <%= field.fieldName %>.blur() + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> should be at least <%= field.fieldValidateRulesMin %>') +<%_ } _%> + +<%_ if (field.fieldTypeNumeric && field.fieldValidationMax) { _%> + await <%= field.fieldName %>.fill('<%= Number(field.fieldValidateRulesMax) + 1 %>') + await <%= field.fieldName %>.blur() + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> cannot be more than <%= field.fieldValidateRulesMax %>') +<%_ } _%> +<%_ } _%> + }) +<%_ } _%> + + test('should navigate back to the <%= entityInstance %> list page', async ({ page }) => { + await page.getByRole('button', { name: 'cancel' }).click() + + await expect(page).toHaveURL('/entities/<%= entityFolderName %>') + const title = page.getByTestId('<%= entityInstance %>Title') + await expect(title).toBeVisible() + await expect(title).toContainText('<%= entityClassPluralHumanized %>') + }) +}) diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs new file mode 100644 index 000000000..484196e05 --- /dev/null +++ b/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs @@ -0,0 +1,123 @@ +<%_ + const entityFakeData = generateFakeData('cypress'); + const containsBinaryField = fields.find(field => (field.fieldTypeBinary && !field.blobContentTypeText)); + const relationshipFields = relationships.filter(relationship => ( + relationship.otherEntity.primaryKey + && entityInstance !== relationship.otherEntity.entityInstance + && (relationship.relationshipManyToOne + || (relationship.relationshipOneToOne && relationship.ownerSide) + || (relationship.relationshipManyToMany && relationship.ownerSide)))) + const containsRelationshipField = relationshipFields && relationshipFields.length; + const servicesApiPrefix = clientRootFolder !== '' ? 'services/' + clientRootFolder + '/' : ''; +_%> +import { expect, test } from '@playwright/test' +import { add<%= entityAngularName %>, prepare<%= entityAngularName %>PrerequisiteData } from '../../../<%= clientRootFolder !== '' ? '../' : '' %>utils/entities/<%= entityFileName %>-utils' +import { deleteRequest, unregisterServiceWorkers } from '../../../<%= clientRootFolder !== '' ? '../' : '' %>utils/test-utils' + +test.describe('<%= entityAngularName %> delete dialog page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + let page + let dynamicId +<%_ for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { +_%> + let <%= relationship.relationshipName %>Id + <%_ + } +} +_%> + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + <%_ if (containsRelationshipField || containsBinaryField ) { _%> + const data = await prepare<%= entityAngularName %>PrerequisiteData(page) + <%_ } _%> + const <%= entityInstance %> = await add<%= entityAngularName %>(page<%_ + if (containsBinaryField) { + _%>, data.binaryData<%_} _%><%_ + if (containsBinaryField || containsRelationshipField) { + _%>,<%_} _%><%_ + let fieldIndex = 0; + const fieldLength = relationshipFields.length; + for (const relationship of relationshipFields) { + if (relationship.otherEntityUser) { + _%>data.user<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } else { + _%>data.<%= relationship.relationshipName %>Id<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } + fieldIndex++; + } + _%>) + dynamicId = <%= entityInstance %>.id + <%_ for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { + _%> + <%= relationship.relationshipName %>Id = data.<%= relationship.relationshipName %>Id + <%_ + } + } +_%> + }) + + test.beforeEach(async () => { + unregisterServiceWorkers() + await page.goto('/entities/<%= entityFolderName %>') + + const rows = page.getByTestId('<%= entityInstance %>Table').getByRole('row') + const targetRow = rows.filter({ hasText: dynamicId }) + await targetRow.hover() + await targetRow.getByRole('button', { name: 'delete' }).click() + }) + + test.afterAll(async () => { + await deleteRequest(page, `<%= servicesApiPrefix %>api/<%= entityApiUrl %>/${dynamicId}`, true) + <%_ + for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { + _%> + await deleteRequest(page, `<%= servicesApiPrefix %>api/<%= relationship.otherEntity.entityApiUrl %>/${<%= relationship.relationshipName %>Id}`, true) + <%_ + } + } + _%> + await page.close() + }) + + test('should display delete <%= entityAngularName %> dialog', async () => { + const modal = page.getByTestId('svlModal') + const heading = modal.getByRole('heading', { level: 1 }) + + await expect(heading).toBeVisible() + await expect(heading).toContainText('Confirm delete operation') + + const text = modal.getByText('Are you sure you want to delete the <%= entityClassHumanized %>?') + await expect(text).toBeVisible() + + await expect(modal.getByRole('button', { name: 'delete' })).toBeEnabled() + await expect(modal.getByRole('button', { name: 'cancel' })).toBeEnabled() + }) + + test('should close the dialog without deleting <%= entityAngularName %>', async () => { + const modal = page.getByTestId('svlModal') + await modal.getByRole('button', { name: 'cancel' }).click() + + const title = page.getByTestId('<%= entityInstance %>Title') + await expect(title).toBeVisible() + await expect(title).toContainText('<%= entityClassPluralHumanized %>') + }) + + test('should delete the <%= entityAngularName %>', async () => { + const modal = page.getByTestId('svlModal') + await modal.getByRole('button', { name: 'delete' }).click() + + <%_ if (!reactive) { _%> + const toast = page.getByTestId('toast-success') + await expect(toast).toContainText('is deleted with identifier') + <%_ } _%> + + const title = page.getByTestId('<%= entityInstance %>Title') + await expect(title).toBeVisible() + await expect(title).toContainText('<%= entityClassPluralHumanized %>') + }) +}) diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs new file mode 100644 index 000000000..12423f733 --- /dev/null +++ b/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs @@ -0,0 +1,178 @@ +<%_ + const entityFakeData = generateFakeData('cypress'); + const containsBinaryField = fields.find(field => (field.fieldTypeBinary && !field.blobContentTypeText)); + const relationshipFields = relationships.filter(relationship => ( + relationship.otherEntity.primaryKey + && entityInstance !== relationship.otherEntity.entityInstance + && (relationship.relationshipManyToOne + || (relationship.relationshipOneToOne && relationship.ownerSide) + || (relationship.relationshipManyToMany && relationship.ownerSide)))) + const containsRelationshipField = relationshipFields && relationshipFields.length; + const oneToOneRelationship = relationshipFields.filter(relationship => relationship.relationshipOneToOne); + const servicesApiPrefix = clientRootFolder !== '' ? 'services/' + clientRootFolder + '/' : ''; +_%> +import { expect, test } from '@playwright/test' +import { add<%= entityAngularName %>, prepare<%= entityAngularName %>PrerequisiteData<% if (!paginationNo || searchEngineAny) { %>, add<%= entityAngularName %>2<% } %> } from '../../../<%= clientRootFolder !== '' ? '../' : '' %>utils/entities/<%= entityFileName %>-utils' +import { deleteRequest, unregisterServiceWorkers } from '../../../<%= clientRootFolder !== '' ? '../' : '' %>utils/test-utils' + +test.describe('<%= entityAngularName %> list page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + let page + let dynamicId +<%_ if (!paginationNo || searchEngineAny) { _%> + let dynamicId2 +<%_ } _%> +<%_ for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { +_%> + let <%= relationship.relationshipName %>Id +<%_ if (relationship.relationshipOneToOne && (!paginationNo || searchEngineAny)) { _%> + let <%= relationship.relationshipName %>Id2 + <%_ + } + } +} +_%> + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + <%_ if (containsRelationshipField || containsBinaryField ) { _%> + const data = await prepare<%= entityAngularName %>PrerequisiteData(page, <%= !!(!paginationNo || searchEngineAny) %>) + <%_ } _%> + <%_ if (!paginationNo || searchEngineAny) { _%> + // create another <%= entityAngularName %> to test sort implementation + const <%= entityInstance %>2 = await add<%= entityAngularName %>2(page<%_ + if (containsBinaryField) { + _%>, data.binaryData<%_ } _%><%_ + if (containsBinaryField || containsRelationshipField) { + _%>,<%_ } _%><%_ + let fieldIndex = 0; + const fieldLength = relationshipFields.length; + for (const relationship of relationshipFields) { + if (relationship.otherEntityUser) { + _%>data.user<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } else { + _%>data.<%= relationship.relationshipName %>Id<%_ if (relationship.relationshipOneToOne) { _%>2<%_ } _%><%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } + fieldIndex++; + } + _%>) + dynamicId2 = <%= entityInstance %>2.id + <%_ for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser && relationship.relationshipOneToOne) { + _%> + <%= relationship.relationshipName %>Id2 = data.<%= relationship.relationshipName %>Id2 + <%_ + } + } + _%> + <%_ } _%> + const <%= entityInstance %> = await add<%= entityAngularName %>(page<%_ + if (containsBinaryField) { + _%>, data.binaryData<%_ } _%><%_ + if (containsBinaryField || containsRelationshipField) { + _%>,<%_ } _%><%_ + let fieldIndex2 = 0; + const fieldLength2 = relationshipFields.length; + for (const relationship of relationshipFields) { + if (relationship.otherEntityUser) { + _%>data.user<%_ if (fieldIndex2 !== (fieldLength2 - 1)) { _%>,<%_ } _%><%_ + } else { + _%>data.<%= relationship.relationshipName %>Id<%_ if (fieldIndex2 !== (fieldLength2 - 1)) { _%>,<%_ } _%><%_ + } + fieldIndex2++; + } + _%>) + dynamicId = <%= entityInstance %>.id + <%_ for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { + _%> + <%= relationship.relationshipName %>Id = data.<%= relationship.relationshipName %>Id + <%_ + } + } + _%> + }) + + test.beforeEach(async () => { + unregisterServiceWorkers() + await page.goto('/entities/<%= entityFolderName %>') + }) + + test.afterAll(async () => { + await deleteRequest(page, `<%= servicesApiPrefix %>api/<%= entityApiUrl %>/${dynamicId}`) +<%_ if (!paginationNo || searchEngineAny) { _%> + await deleteRequest(page, `<%= servicesApiPrefix %>api/<%= entityApiUrl %>/${dynamicId2}`) +<%_ } _%> +<%_ for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { +_%> + await deleteRequest(page, `<%= servicesApiPrefix %>api/<%= relationship.otherEntity.entityApiUrl %>/${<%= relationship.relationshipName %>Id}`, true) +<%_ if (relationship.relationshipOneToOne && (!paginationNo || searchEngineAny)) { _%> + await deleteRequest(page, `<%= servicesApiPrefix %>api/<%= relationship.otherEntity.entityApiUrl %>/${<%= relationship.relationshipName %>Id2}`, true) +<%_ + } + } +} +_%> + await page.close() + }) + + test('should greet with <%= entityAngularName %> page title', async () => { + const title = page.getByTestId('<%= entityInstance %>Title') + await expect(title).toBeVisible() + await expect(title).toContainText('<%= entityClassPluralHumanized %>') + }) + + test('should display <%= entityAngularName %> table', async () => { + const table = page.getByTestId('<%= entityInstance %>Table') + await expect(table).toBeVisible() + + const firstRow = table.getByRole('row').first() + const headers = firstRow.getByRole('columnheader') + +<%_ for (let loopIndex = 0; loopIndex > fields.length; loopIndex++) { + const field = fields[loopIndex]; +_%> + await expect(headers).toHaveCount(<%= fields.length %>) + await expect(headers.nth(<%= loopIndex %>)).toContainText('<%= field.fieldNameHumanized %>') +<%_ } _%> + }) + + test('should display <%= entityAngularName %> record in the table', async () => { + const firstRow = page.getByTestId('<%= entityInstance %>Table').locator('tbody tr').first() +<%_ for (let loopIndex = 0; loopIndex > fields.length; loopIndex++) { _%> + await expect(firstRow.locator('td').nth(<%= loopIndex %>)).not.toBeEmpty() +<%_ } _%> + }) + +<%_ if (!paginationNo) { _%> + test('should validate the pagination controls', async () => { + const pageControl = page.getByTestId('pageCtrl').first() + await expect(pageControl).toContainText(/1-\d+ of \d+/) + + const pagination = pageControl.locator('div').nth(1) + await expect(pagination.getByTestId('prevPageCtrl')).toBeDisabled() + await expect(pagination.locator('div')).toHaveText('1') + await expect(pagination.getByTestId('nextPageCtrl')).toBeDisabled() + }) + + test('should sort the records by Id field', async () => { + let valueBeforeSort + + const firstCell = page.getByTestId('<%= entityInstance %>Table').getByRole('row').nth(1).getByRole('cell').first() + valueBeforeSort = await firstCell.textContent() + + const idHeader = page + .getByTestId('<%= entityInstance %>Table') + .getByRole('row') + .first() + .getByRole('columnheader') + .first() + await idHeader.getByRole('button').click() + + await expect(firstCell).not.toHaveText(valueBeforeSort) + }) +<%_ } _%> +}) \ No newline at end of file diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs new file mode 100644 index 000000000..8802c86bc --- /dev/null +++ b/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs @@ -0,0 +1,256 @@ +<%_ + const entityFakeData = generateFakeData('cypress'); + const containsBinaryField = fields.find(field => (field.fieldTypeBinary && !field.blobContentTypeText)); + const containsMandatoryField = fields.find(field => field.fieldValidationRequired); + const containsFieldValidation = containsMandatoryField || fields.find(field => (field.fieldValidationMinLength + || field.fieldValidationMaxLength + || field.fieldValidationMin + || field.fieldValidationMax + || (!field.fieldValidationRequired && field.fieldTypeNumeric))); + const placeHolderText = 'lorem ipsum'; + const getMinLengthText = function(len) { + return placeHolderText.substr(0, len > 1 ? len-1 : 0); + } + const getMaxLengthText = function(len) { + let maxLengthText = placeHolderText; + while(maxLengthText.length < len) { + maxLengthText+= placeHolderText; + } + return maxLengthText; + } + const relationshipFields = relationships.filter(relationship => ( + relationship.otherEntity.primaryKey + && entityInstance !== relationship.otherEntity.entityInstance + && (relationship.relationshipManyToOne + || (relationship.relationshipOneToOne && relationship.ownerSide) + || (relationship.relationshipManyToMany && relationship.ownerSide)))) + const containsRelationshipField = relationshipFields && relationshipFields.length; + const servicesApiPrefix = clientRootFolder !== '' ? 'services/' + clientRootFolder + '/' : ''; +_%> +import { expect, test } from '@playwright/test' +import { add<%= entityAngularName %>, prepare<%= entityAngularName %>PrerequisiteData } from '../../../<%= clientRootFolder !== '' ? '../' : '' %>utils/entities/<%= entityFileName %>-utils' +import { deleteRequest, getByName, setFileInput, unregisterServiceWorkers } from '../../../<%= clientRootFolder !== '' ? '../' : '' %>utils/test-utils' + +test.describe('Update <%= entityInstance %> page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + let page + let dynamicId +<%_ for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { +_%> + let <%= relationship.relationshipName %>Id + <%_ + } +} +_%> + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() +<%_ if (containsRelationshipField || containsBinaryField ) { _%> + const data = await prepare<%= entityAngularName %>PrerequisiteData(page) +<%_ } _%> + const <%= entityInstance %> = await add<%= entityAngularName %>(page<%_ + if (containsBinaryField) { + _%>, data.binaryData<%_} _%><%_ + if (containsBinaryField || containsRelationshipField) { + _%>,<%_} _%><%_ + let fieldIndex = 0; + const fieldLength = relationshipFields.length; + for (const relationship of relationshipFields) { + if (relationship.otherEntityUser) { + _%>data.user<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } else { + _%>data.<%= relationship.relationshipName %>Id<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } + fieldIndex++; + } + _%>) + dynamicId = <%= entityInstance %>.id + <%_ for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { + _%> + <%= relationship.relationshipName %>Id = data.<%= relationship.relationshipName %>Id + <%_ + } + } + _%> + }) + + test.beforeEach(async () => { + unregisterServiceWorkers() + await page.goto(`/entities/<%= entityFolderName %>/${dynamicId}/edit`) + }) + + test.afterAll(async () => { + await deleteRequest(page, `<%= servicesApiPrefix %>api/<%= entityApiUrl %>/${dynamicId}`) + <%_ + for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { + _%> + await deleteRequest(page, `<%= servicesApiPrefix %>api/<%= relationship.otherEntity.entityApiUrl %>/${<%= relationship.relationshipName %>Id}`, true) + <%_ + } + } + _%> + await page.close() + }) + + test('should greet with update <%= entityInstance %> title', async () => { + const title = page.getByTestId('<%= entityInstance %>Title') + await expect(title).toBeVisible() + await expect(title).toContainText('Update <%= entityClassHumanized %>') + }) + + test('should be populated and have valid state', async () => { + const form = page.getByTestId('add<%= entityAngularName %>Form') + <%_ + for (field of fields.filter(field => !field.id)) { + _%> + <%_ if (!field.fieldTypeTimed && !field.fieldTypeDuration && !field.fieldTypeBoolean) { _%> + <%_ if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + await expect(form.getByTestId('<%= field.fieldName %>View')).toContainText('image/png'); + <%_ } else { _%> + await expect(getByName(form, '<%= field.fieldName %>')).not.toBeEmpty() + <%_ } _%> + <%_ } + } + _%> + await expect(form.getByText('Update <%= entityClassHumanized %>')).toBeEnabled() + }) + +<%_ if (containsFieldValidation) { _%> + test('should validate field values', async () => { + const form = page.getByTestId('add<%= entityAngularName %>Form') +<%_ + for (field of fields.filter(field => !field.id)) { + const fieldValue = !entityFakeData ? field.generateFakeData('cypress') : entityFakeData[field.fieldName]; + if (fieldValue === undefined) { + warning(`Error generating a value for field ${field.fieldName}`); + } +_%> + const <%= field.fieldName %> = getByName(form, '<%= field.fieldName %>') + const <%= field.fieldName %>Error = form.getByTestId('<%= field.fieldName %>-error') + +<%_ if (field.fieldValidationRequired) { _%> + await <%= field.fieldName %>.fill('<%= fieldValue %>') + await <%= field.fieldName %>.clear() + await <%= field.fieldName %>.blur() + + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> is mandatory') +<%_ } _%> + +<%_ if (field.fieldValidationMinLength) { _%> + await <%= field.fieldName %>.clear() + await <%= field.fieldName %>.fill('<%= getMinLengthText(field.fieldValidateRulesMinlength) %>') + await <%= field.fieldName %>.blur() + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> should be greater than <%= field.fieldValidateRulesMinlength %> characters') +<%_ } _%> + +<%_ if (field.fieldValidationMaxLength) { _%> + await <%= field.fieldName %>.clear() + await <%= field.fieldName %>.fill('<%= getMaxLengthText(field.fieldValidateRulesMaxlength) %>') + await <%= field.fieldName %>.blur() + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> cannot be longer than <%= field.fieldValidateRulesMaxlength %> characters') +<%_ } _%> + +<%_ if (field.fieldTypeNumeric && !field.fieldValidationRequired) { _%> + await <%= field.fieldName %>.fill('1') + await <%= field.fieldName %>.clear() + await <%= field.fieldName %>.fill('-') + await <%= field.fieldName %>.blur() + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> should be numeric') +<%_ } _%> + +<%_ if (field.fieldTypeNumeric && field.fieldValidationMin) { _%> + await <%= field.fieldName %>.fill('<%= Number(field.fieldValidateRulesMin) - 1 %>') + await <%= field.fieldName %>.blur() + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> should be at least <%= field.fieldValidateRulesMin %>') +<%_ } _%> + +<%_ if (field.fieldTypeNumeric && field.fieldValidationMax) { _%> + await <%= field.fieldName %>.fill('<%= Number(field.fieldValidateRulesMax) + 1 %>') + await <%= field.fieldName %>.blur() + await expect(<%= field.fieldName %>Error).toBeVisible() + await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> cannot be more than <%= field.fieldValidateRulesMax %>') +<%_ } _%> +<%_ } _%> + }) +<%_ } _%> + + test('should navigate back to the <%= entityInstance %> list page', async () => { + await page.getByRole('button', { name: 'cancel' }).click() + + await expect(page).toHaveURL('/entities/<%= entityFolderName %>') + const title = page.getByTestId('<%= entityInstance %>Title') + await expect(title).toBeVisible() + await expect(title).toContainText('<%= entityClassPluralHumanized %>') + }) + + test('should update <%= entityInstance %> details', async () => { + const form = page.getByTestId('add<%= entityAngularName %>Form') + + <%_ + for (field of fields.filter(field => !field.id)) { + const fieldValue = !entityFakeData ? field.generateFakeData('cypress') : entityFakeData[field.fieldName]; + _%> + <%_ if (!field.fieldTypeDuration) {_%> + <%_ if (field.fieldTypeBoolean) {_%> + <%_ if (fieldValue === true ) { _%> + await getByName(form, '<%= field.fieldName %>').uncheck() + <%_ } else { _%> + await getByName(form, '<%= field.fieldName %>').check() + <%_ } _%> + <%_ } else if (field.fieldIsEnum) { _%> + await getByName(form, '<%= field.fieldName %>').click() + const optionsContainer = form.getByTestId('<%= field.fieldName %>-options') + await optionsContainer.locator("input[type='checkbox']").nth(0).check() + await optionsContainer.locator("input[type='checkbox']").nth(1).check() + await optionsContainer.press('Escape') + <%_ } else if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + await getByName(form, '<%= field.fieldName %>ClearBtn').click() + await setFileInput( + getByName(form, '<%= field.fieldName %>'), + 'integration-test.png', + 'image/png' + ); + <%_ } else { _%> + await getByName(form, '<%= field.fieldName %>').clear() + await getByName(form, '<%= field.fieldName %>').fill('<%= fieldValue %>') + <%_ } _%> + <%_ } _%> + <%_ } _%> + + <%_ + for (const relationship of relationshipFields) { + let relationshipFieldKey + if (relationship.relationshipManyToMany) { + relationshipFieldKey = relationship.relationshipNamePlural; + } else { + relationshipFieldKey = relationship.relationshipName; + } + _%> + await getByName(form, '<%= relationshipFieldKey %>').click() + await form.getByTestId('<%= relationshipFieldKey %>-bg').press('Escape') + <%_ + } + _%> + + const updateButton = form.getByText('Update <%= entityClassHumanized %>') + await expect(updateButton).toBeEnabled() + await updateButton.click() + + const toast = page.getByTestId('toast-success') + await expect(toast).toContainText('is updated with identifier') + + await expect(page).toHaveURL('/entities/<%= entityFolderName %>') + const title = page.getByTestId('<%= entityInstance %>Title') + await expect(title).toBeVisible() + await expect(title).toContainText('<%= entityClassPluralHumanized %>') + }) +}) \ No newline at end of file diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs new file mode 100644 index 000000000..4d8c08dca --- /dev/null +++ b/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs @@ -0,0 +1,104 @@ +<%_ + const entityFakeData = generateFakeData('cypress'); + const containsBinaryField = fields.find(field => (field.fieldTypeBinary && !field.blobContentTypeText)); + const relationshipFields = relationships.filter(relationship => ( + relationship.otherEntity.primaryKey + && entityInstance !== relationship.otherEntity.entityInstance + && (relationship.relationshipManyToOne + || (relationship.relationshipOneToOne && relationship.ownerSide) + || (relationship.relationshipManyToMany && relationship.ownerSide)))) + const relationshipFieldsWithSelfReferential = relationships.filter(relationship => ( + relationship.otherEntity.primaryKey + && (relationship.relationshipManyToOne + || (relationship.relationshipOneToOne && relationship.ownerSide) + || (relationship.relationshipManyToMany && relationship.ownerSide)))) + const containsRelationshipField = relationshipFields && relationshipFields.length; + const servicesApiPrefix = clientRootFolder !== '' ? 'services/' + clientRootFolder + '/' : ''; +_%> +import { expect, test } from '@playwright/test' +import { add<%= entityAngularName %>, prepare<%= entityAngularName %>PrerequisiteData } from '../../../<%= clientRootFolder !== '' ? '../' : '' %>utils/entities/<%= entityFileName %>-utils' +import { deleteRequest, unregisterServiceWorkers } from '../../../<%= clientRootFolder !== '' ? '../' : '' %>utils/test-utils' + +test.describe('<%= entityAngularName %> view details page', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + let page + let dynamicId +<%_ for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { +_%> + let <%= relationship.relationshipName %>Id + <%_ + } +} +_%> + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() +<%_ if (containsRelationshipField || containsBinaryField ) { _%> + const data = await prepare<%= entityAngularName %>PrerequisiteData(page) +<%_ } _%> + const <%= entityInstance %> = await add<%= entityAngularName %>(page<%_ + if (containsBinaryField) { + _%>, data.binaryData<%_} _%><%_ + if (containsBinaryField || containsRelationshipField) { + _%>,<%_} _%><%_ + let fieldIndex = 0; + const fieldLength = relationshipFields.length; + for (const relationship of relationshipFields) { + if (relationship.otherEntityUser) { + _%>data.user<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } else { + _%>data.<%= relationship.relationshipName %>Id<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } + fieldIndex++; + } + _%>) + dynamicId = <%= entityInstance %>.id + <%_ for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { + _%> + <%= relationship.relationshipName %>Id = data.<%= relationship.relationshipName %>Id + <%_ + } + } + _%> + }) + + test.beforeEach(async () => { + unregisterServiceWorkers() + await page.goto(`/entities/<%= entityFolderName %>/${dynamicId}/view`) + }) + + test.afterAll(async () => { + await deleteRequest(page, `<%= servicesApiPrefix %>api/<%= entityApiUrl %>/${dynamicId}`) + <%_ + for (const relationship of relationshipFields) { + if (!relationship.otherEntityUser) { + _%> + await deleteRequest(page, `<%= servicesApiPrefix %>api/<%= relationship.otherEntity.entityApiUrl %>/${<%= relationship.relationshipName %>Id}`, true) + <%_ + } + } + _%> + await page.close() + }) + + test('should display <%= entityAngularName %> details', async () => { + const title = page.getByTestId('<%= entityInstance %>Title') + await expect(title).toBeVisible() + await expect(title).toContainText('<%= entityClassHumanized %> Details') + + const table = page.locator('div.table') + const cells = table.locator('.table-cell') + await expect(cells).toHaveCount(<%= (fields.length - 1) * 2 + (relationshipFieldsWithSelfReferential.length * 2) %>) + }) + + test('should navigate back to the <%= entityAngularName %> list page', async () => { + await page.getByRole('button', { name: 'back' }).click() + + const title = page.getByTestId('<%= entityInstance %>Title') + await expect(title).toBeVisible() + await expect(title).toContainText('<%= entityClassPluralHumanized %>') + }) +}) diff --git a/generators/client/templates/playwright/integration/footer.spec.js.ejs b/generators/client/templates/playwright/integration/footer.spec.js.ejs new file mode 100644 index 000000000..640d51da7 --- /dev/null +++ b/generators/client/templates/playwright/integration/footer.spec.js.ejs @@ -0,0 +1,16 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../utils/test-utils' + +test.describe('Footer', () => { + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/') + }) + + test('should display copyright message', async ({ page }) => { + await expect(page.getByTestId('copyrightMsg')).toBeVisible() + await expect(page.getByTestId('copyrightMsg')).toContainText( + 'Copyright © 2023 JHipster. All Rights Reserved', + ) + }) +}) diff --git a/generators/client/templates/playwright/integration/home.spec.js.ejs b/generators/client/templates/playwright/integration/home.spec.js.ejs new file mode 100644 index 000000000..720e592f6 --- /dev/null +++ b/generators/client/templates/playwright/integration/home.spec.js.ejs @@ -0,0 +1,50 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../utils/test-utils' + +test.describe('Home page', () => { + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/') + }) + + test('should greet with welcome title', async ({ page }) => { + const welcomeTitle = page.getByTestId('welcomeTitle') + await expect(welcomeTitle).toBeVisible() + await expect(welcomeTitle).toContainText('Welcome, JHipster Svelte!') + }) + + test.describe('unauthenticated user', () => { + test('should have login instructions', async ({ page }) => { + const instructions = page.getByTestId('loginInstructions') + await expect(instructions).toBeVisible() + await expect(instructions).toContainText('you can try the default accounts') + await expect(instructions).toContainText('Administrator (login="admin" and password="admin")') + await expect(instructions).toContainText('User (login="user" and password="user").') + }) + + <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> + test('should have user registration link', async ({ page }) => { + const registerLink = page.getByTestId('svlRegisterHomeLink') + await expect(registerLink).toBeVisible() + await expect(registerLink).toHaveAttribute('href', '/account/register') + await expect(registerLink).toContainText('Register a new account') + }) + <%_ } _%> + }) + + test.describe('authenticated user', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/') + }) + + test('should greet logged in user', async ({ page }) => { + await expect(page.getByTestId('greetMsg')).toBeVisible() + await expect(page.getByTestId('greetMsg')).toContainText( + `You are logged in as user "${process.env.ADMIN_USERNAME}".`, + ) + }) + }) +}) diff --git a/generators/client/templates/playwright/integration/login.spec.js.ejs b/generators/client/templates/playwright/integration/login.spec.js.ejs new file mode 100644 index 000000000..6e1df12ad --- /dev/null +++ b/generators/client/templates/playwright/integration/login.spec.js.ejs @@ -0,0 +1,74 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../utils/test-utils' + +test.describe('User login', () => { + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/login') + }) + + test('should greet with Sign in', async ({ page }) => { + const signInTitle = page.getByTestId('signInTitle') + await expect(signInTitle).toBeVisible() + await expect(signInTitle).toContainText('Sign in to <%= baseName %>') + }) + + <%_ if (!skipUserManagement) { _%> + test('should display link to register', async ({ page }) => { + const registerLink = page.getByTestId('registerLink') + await expect(registerLink).toHaveText('Create an account') + await expect(registerLink).toHaveAttribute('href', '/account/register') + }) + + test('should display link to forgot password', async ({ page }) => { + const forgotPwdLink = page.getByTestId('forgotPwdLink') + await expect(forgotPwdLink).toHaveText('Forgot password?') + await expect(forgotPwdLink).toHaveAttribute('href', '/account/reset/init') + }) + <%_ } _%> + + test('should require username and password', async ({ page }) => { + await expect( + page.getByTestId('loginForm').getByRole('button', { name: 'Sign in' }), + ).toBeDisabled() + }) + + test('should require username', async ({ page }) => { + const usernameInput = page.getByTestId('loginForm').getByRole('textbox', { name: 'username' }) + await usernameInput.fill('admin') + await usernameInput.clear() + await usernameInput.blur() + + const usernameError = page.getByTestId('username-error') + await expect(usernameError).toBeVisible() + await expect(usernameError).toContainText('Username is mandatory') + }) + + test('should require password', async ({ page }) => { + const passwordInput = page.getByTestId('loginForm').getByRole('textbox', { name: 'password' }) + await passwordInput.fill('admin') + await passwordInput.clear() + await passwordInput.blur() + + const passwordError = page.getByTestId('password-error') + await expect(passwordError).toBeVisible() + await expect(passwordError).toContainText('Password is mandatory') + }) + + test('should require a valid username and password', async ({ page }) => { + await page.getByTestId('loginForm').getByRole('textbox', { name: 'username' }).fill('admin') + await page.getByTestId('loginForm').getByRole('textbox', { name: 'password' }).fill('invalid') + await page.keyboard.press('Enter') + + await expect(page.getByTestId('errorMsg')).toContainText('Incorrect username or password.') + }) + + test('should navigate to / on successful login', async ({ page }) => { + await page.getByTestId('loginForm').getByLabel('Remember me').check() + await page.getByTestId('loginForm').getByRole('textbox', { name: 'username' }).fill('admin') + await page.getByTestId('loginForm').getByRole('textbox', { name: 'password' }).fill('admin') + await page.keyboard.press('Enter') + + await expect(page).toHaveURL('/') + }) +}) diff --git a/generators/client/templates/playwright/integration/navbar.spec.js.ejs b/generators/client/templates/playwright/integration/navbar.spec.js.ejs new file mode 100644 index 000000000..c1428ae9d --- /dev/null +++ b/generators/client/templates/playwright/integration/navbar.spec.js.ejs @@ -0,0 +1,178 @@ +import { expect, test } from '@playwright/test' +import { loginByApi, unregisterServiceWorkers } from '../utils/test-utils' + +test.describe('Navbar', () => { + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/') + }) + + test.describe('unauthenticated user', () => { + test('should display application name', async ({ page }) => { + await expect(page.getByTestId('svlAppName')).toBeVisible() + await expect(page.getByTestId('svlAppName')).toContainText('<%= baseName %>') + }) + + test('should display application version', async ({ page }) => { + await expect(page.getByTestId('svlAppVersion')).toBeVisible() + await expect(page.getByTestId('svlAppVersion')).toContainText('DEV') + }) + + test('should not display navigation toggle button', async ({ page }) => { + await expect(page.getByTestId('svlNavBtn')).toBeHidden() + }) + + test('should not display account menu', async ({ page }) => { + await expect(page.getByTestId('svlAcctMenu')).toBeHidden() + }) + + test('should display sign in link', async ({ page }) => { + await expect(page.getByTestId('svlLoginLink')).toBeVisible() + await expect(page.getByTestId('svlLoginLink')).toContainText('Sign in') + }) + + <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> + test('should display register link', async ({ page }) => { + const registerLink = page.getByTestId('svlRegisterLink') + await expect(registerLink).toBeVisible() + await expect(registerLink).toContainText('Sign up') + }) + <%_ } _%> + }) + + test.describe('authenticated user', () => { + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) + await page.goto('/') + }) + + <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> + test('should not display register link', async ({ page }) => { + await expect(page.getByTestId('svlRegisterLink')).toBeHidden() + }) + <%_ } _%> + + test('should not display sign in link', async ({ page }) => { + await expect(page.getByTestId('svlLoginLink')).toBeHidden() + }) + + test('should display account menu', async ({ page }) => { + const accountMenu = page.getByTestId('svlAcctMenu') + await expect(accountMenu).toBeVisible() + + const accountLink = accountMenu.getByTestId('svlAccountLink') + await expect(accountLink).toBeEnabled() + await accountLink.click() + + <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> + const passwordLink = page.getByTestId('svlChgPwdLink') + await expect(passwordLink).toBeVisible() + await expect(passwordLink).toHaveAttribute('href', '/account/password') + await expect(passwordLink).toContainText('Change Password') + + const settingsLink = page.getByTestId('svlSettingsLink') + await expect(settingsLink).toBeVisible() + await expect(settingsLink).toHaveAttribute('href', '/account/settings') + await expect(settingsLink).toContainText('Settings') + <%_ } _%> + + const signoutLink = page.getByTestId('svlSignoutLink') + await expect(signoutLink).toBeVisible() + await expect(signoutLink).toHaveAttribute('href', '/') + await expect(signoutLink).toContainText('Sign out') + }) + + test('should display administrator menu', async ({ page }) => { + const adminMenu = page.getByTestId('svlAdminMenu') + await expect(adminMenu).toBeVisible() + + const adminLink = adminMenu.getByTestId('svlAdminLink') + await expect(adminLink).toBeEnabled() + await adminLink.click() + + const loggerLink = page.getByTestId('svlLoggerLink') + await expect(loggerLink).toBeVisible() + await expect(loggerLink).toHaveAttribute('href', '/admin/logger') + await expect(loggerLink).toContainText('Loggers') + + <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> + const userMgmtLink = page.getByTestId('svlUserMgmtLink') + await expect(userMgmtLink).toBeVisible() + await expect(userMgmtLink).toHaveAttribute('href', '/admin/user-management') + await expect(userMgmtLink).toContainText('User Management') + <%_ } _%> + }) + + <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> + test('should navigate to change password page', async ({ page }) => { + await page.getByTestId('svlAcctMenu').getByTestId('svlAccountLink').click() + const passwordLink = page.getByTestId('svlChgPwdLink') + await expect(passwordLink).toBeVisible() + await passwordLink.click() + + await expect(page).toHaveURL('/account/password') + }) + + test('should navigate to settings page', async ({ page }) => { + await page.getByTestId('svlAcctMenu').getByTestId('svlAccountLink').click() + const settingsLink = page.getByTestId('svlSettingsLink') + await expect(settingsLink).toBeVisible() + await settingsLink.click() + + await expect(page).toHaveURL('/account/settings') + }) + <%_ } _%> + + <%_ if (authenticationType !== 'oauth2') { _%> + test('should logout user', async ({ page }) => { + await page.getByTestId('svlAcctMenu').getByTestId('svlAccountLink').click() + const signoutLink = page.getByTestId('svlSignoutLink') + await expect(signoutLink).toBeVisible() + await signoutLink.click() + + await expect(page).toHaveURL('/') + await expect(page.getByTestId('svlLoginLink')).toBeVisible() + await expect(page.getByTestId('svlLoginLink')).toContainText('Sign in') + }) + <%_ } _%> + }) + + <%_ if (!skipUserManagement || authenticationType === 'oauth2') { _%> + test.describe(`authenticated 'ROLE_USER' ROLE user`, () => { + test.use({ storageState: 'playwright/.auth/user.json' }) + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/') + }) + + test('should not display administrator menu', async ({ page }) => { + await expect(page.getByTestId('svlAdminMenu')).toBeHidden() + }) + }) + <%_ } _%> + + <%_ if (authenticationType === 'oauth2') { _%> + test.describe('Logout authenticated user', () => { + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) + await page.goto('/') + }) + + test('should logout user', async ({ page }) => { + await page.getByTestId('svlAcctMenu').getByTestId('svlAccountLink').click() + + const signOutLink = page.getByTestId('svlSignoutLink') + await expect(signOutLink).toBeVisible() + await signOutLink.click() + + await expect(page).toHaveURL('/') + const signInLink = page.getByTestId('svlLoginLink') + await expect(signInLink).toBeVisible() + await expect(signInLink).toHaveText('Sign in') + }) + }) + <%_ } _%> +}) diff --git a/generators/client/templates/playwright/integration/routes.spec.js.ejs b/generators/client/templates/playwright/integration/routes.spec.js.ejs new file mode 100644 index 000000000..ee34a6ff2 --- /dev/null +++ b/generators/client/templates/playwright/integration/routes.spec.js.ejs @@ -0,0 +1,114 @@ +import { expect, test } from '@playwright/test' +import { unregisterServiceWorkers } from '../utils/test-utils' + +test.describe('Routes', () => { + test.beforeEach(async () => { + unregisterServiceWorkers() + }) + + test.describe('unauthenticated user', () => { + <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> + test('should not allow navigation to settings page', async ({ page }) => { + await page.goto('/account/settings') + + await expect(page).toHaveURL('/login') + const signInTitle = page.getByTestId('signInTitle') + await expect(signInTitle).toBeVisible() + await expect(signInTitle).toContainText('Sign in to <%= baseName %>') + }) + + test('should not allow navigation to user management page', async ({ page }) => { + await page.goto('/admin/user-management') + + await expect(page).toHaveURL('/login') + const signInTitle = page.getByTestId('signInTitle') + await expect(signInTitle).toBeVisible() + await expect(signInTitle).toContainText('Sign in to <%= baseName %>') + }) + <%_ } _%> + + <%_ if (authenticationType !== 'oauth2') { _%> + test('should not allow navigation to Loggers page', async ({ page }) => { + await page.goto('/admin/logger') + + await expect(page).toHaveURL('/login') + await expect(page.getByTestId('signInTitle')).toBeVisible() + await expect(page.getByTestId('signInTitle')).toContainText('Sign in to <%= baseName %>') + }) + <%_ } _%> + + test('should allow navigation to home page', async ({ page }) => { + await page.goto('/') + + await expect(page).toHaveURL('/') + await expect(page.getByTestId('welcomeTitle')).toBeVisible() + await expect(page.getByTestId('welcomeTitle')).toContainText('Welcome, JHipster Svelte!') + }) + }) + + test.describe('authenticated user', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }) + + test.beforeEach(async ({ page }) => { + unregisterServiceWorkers() + await page.goto('/') + }) + + test('should not allow navigation to login page', async ({ page }) => { + <%_ if (authenticationType === 'oauth2') { _%> + await page.goto('/oauth2/authorization/oidc') + <%_ } else { _%> + await page.goto('/login') + <%_ } _%> + + await expect(page).toHaveURL('/') + await expect(page.getByTestId('welcomeTitle')).toBeVisible() + await expect(page.getByTestId('welcomeTitle')).toContainText('Welcome, JHipster Svelte!') + }) + + <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> + test('should not allow navigation to register page', async ({ page }) => { + await page.goto('/account/register') + + await expect(page).toHaveURL('/') + const welcomeTitle = page.getByTestId('welcomeTitle') + await expect(welcomeTitle).toBeVisible() + await expect(welcomeTitle).toContainText('Welcome, JHipster Svelte!') + }) + <%_ } _%> + + test('should allow navigation to home page', async ({ page }) => { + await page.goto('/') + + await expect(page).toHaveURL('/') + await expect(page.getByTestId('welcomeTitle')).toBeVisible() + await expect(page.getByTestId('welcomeTitle')).toContainText('Welcome, JHipster Svelte!') + }) + }) + + <%_ if (authenticationType !== 'oauth2') { _%> + test.describe('navigation context', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/admin/logger') + await expect(page).toHaveURL('/login') + }) + + test('should navigate to saved context', async ({ page }) => { + await page.getByTestId('loginForm').getByLabel('Remember me').check() + await page + .getByTestId('loginForm') + .getByRole('textbox', { name: 'username' }) + .fill(process.env.ADMIN_USERNAME) + await page + .getByTestId('loginForm') + .getByRole('textbox', { name: 'password' }) + .fill(process.env.ADMIN_PASSWORD) + await page.keyboard.press('Enter') + + await expect(page).toHaveURL('/admin/logger') + await expect(page.getByTestId('loggersTitle')).toBeVisible() + await expect(page.getByTestId('loggersTitle')).toContainText('Loggers') + }) + }) + <%_ } _%> +}) diff --git a/generators/client/templates/playwright/package.json b/generators/client/templates/playwright/package.json new file mode 100644 index 000000000..d0088f2b6 --- /dev/null +++ b/generators/client/templates/playwright/package.json @@ -0,0 +1,12 @@ +{ + "scripts": { + "e2e": "npm run e2e:ci -- --headed", + "e2e:ci": "playwright test", + "e2e:ui": "playwright test --ui" + }, + "devDependencies": { + "@playwright/test": "1.49.1", + "dotenv": "16.4.7", + "eslint-plugin-playwright": "2.2.0" + } +} diff --git a/generators/client/templates/playwright/utils/entities/entity-util.js.ejs b/generators/client/templates/playwright/utils/entities/entity-util.js.ejs new file mode 100644 index 000000000..029b0d8b0 --- /dev/null +++ b/generators/client/templates/playwright/utils/entities/entity-util.js.ejs @@ -0,0 +1,242 @@ +<%_ + const entityFakeData = generateFakeData('cypress'); + const containsBinaryField = fields.find(field => (field.fieldTypeBinary && !field.blobContentTypeText)); + const relationshipFields = relationships.filter(relationship => ( + relationship.otherEntity.primaryKey + && entityInstance !== relationship.otherEntity.entityInstance + && (relationship.relationshipManyToOne + || (relationship.relationshipOneToOne && relationship.ownerSide) + || (relationship.relationshipManyToMany && relationship.ownerSide)))) + const containsRelationshipField = relationshipFields && relationshipFields.length; + + const oneToOneRelationship = relationshipFields.find(relationship => relationship.relationshipOneToOne); + const servicesApiPrefix = clientRootFolder !== '' ? 'services/' + clientRootFolder + '/' : ''; +_%> +import { getById, save } from '../test-utils' +<%_ if (containsBinaryField) { _%> +import fs from 'node:fs'; +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +<%_ } _%> +export async function prepare<%= entityAngularName %>PrerequisiteData(page, listPage = false) { +<%_ if (containsBinaryField) { _%> + const imagePath = path.join(__dirname, '../../fixtures/integration-test.png') + const binaryData = fs.readFileSync(imagePath, { encoding: 'base64' }) +<%_ } _%> +<%_ for (const relationship of relationshipFields) { _%> + <%_ if (relationship.otherEntityUser) { _%> + const user = await getById(page, 'api/users') + <%_ } else { _%> + const <%= relationship.relationshipName %> = await save(page, '<%= servicesApiPrefix %>api/<%= relationship.otherEntity.entityApiUrl %>', { + <%_ for (const relationshipField of relationship.otherEntity.fields.filter(field => !field.id)) { + const fieldValue = relationshipField.generateFakeData('cypress'); + if (fieldValue === undefined) { + warning(`Error generating a value for field ${relationshipField.fieldName}`); + } + _%> + <%_ if (relationshipField.fieldTypeBoolean) { _%> + <%= relationshipField.fieldName %>: true, + <%_ } else if (relationshipField.fieldTypeBinary && !relationshipField.blobContentTypeText) { _%> + // <%= relationshipField.fieldName %>: binaryData, + <%= relationshipField.fieldName %>ContentType: 'image/png', + <%_ } else if (relationshipField.fieldTypeString || relationshipField.fieldTypeUUID) { _%> + <%= relationshipField.fieldName %>: '<%= fieldValue %>', + <%_ } else if (relationshipField.fieldTypeLocalDate) { _%> + <%= relationshipField.fieldName %>: '<%= fieldValue %>' , + <%_ } else if (relationshipField.fieldTypeTimed) { _%> + <%= relationshipField.fieldName %>: '<%= fieldValue %>:00.000Z' , + <%_ } else if (relationshipField.fieldTypeDuration) { _%> + <%= relationshipField.fieldName %>: 'PT0.000052484S', + <%_ } else if (relationshipField.fieldTypeNumeric) { _%> + <%= relationshipField.fieldName %>: <%= fieldValue %>, + <%_ } else { _%> + <%= relationshipField.fieldName %>: '<%= fieldValue %>', + <%_ } _%> + <%_ } _%> + }) +<%_ } +} + if (oneToOneRelationship) { _%> + if (listPage) { + const <%= oneToOneRelationship.relationshipName %>2 = await save(page, '<%= servicesApiPrefix %>api/<%= oneToOneRelationship.otherEntity.entityApiUrl %>', { + <%_ for (const relationshipField of oneToOneRelationship.otherEntity.fields.filter(field => !field.id)) { + const fieldValue = relationshipField.generateFakeData('cypress'); + if (fieldValue === undefined) { + warning(`Error generating a value for field ${relationshipField.fieldName}`); + } + _%> + <%_ if (relationshipField.fieldTypeBoolean) { _%> + <%= relationshipField.fieldName %>: true, + <%_ } else if (relationshipField.fieldTypeBinary && !relationshipField.blobContentTypeText) { _%> + // <%= relationshipField.fieldName %>: binaryData, + <%= relationshipField.fieldName %>ContentType: 'image/png', + <%_ } else if (relationshipField.fieldTypeString || relationshipField.fieldTypeUUID) { _%> + <%= relationshipField.fieldName %>: '<%= fieldValue %>', + <%_ } else if (relationshipField.fieldTypeLocalDate) { _%> + <%= relationshipField.fieldName %>: '<%= fieldValue %>' , + <%_ } else if (relationshipField.fieldTypeTimed) { _%> + <%= relationshipField.fieldName %>: '<%= fieldValue %>:00.000Z' , + <%_ } else if (relationshipField.fieldTypeDuration) { _%> + <%= relationshipField.fieldName %>: 'PT0.000052484S', + <%_ } else if (relationshipField.fieldTypeNumeric) { _%> + <%= relationshipField.fieldName %>: <%= fieldValue %>, + <%_ } else { _%> + <%= relationshipField.fieldName %>: '<%= fieldValue %>', + <%_ } _%> + <%_ } _%> + }) + } +<%_ } _%> + return { + <%_ if (containsBinaryField) { _%> + binaryData, + <%_ } _%> + <%_ for (const relationship of relationshipFields) { _%> + <%_ if (relationship.otherEntityUser) { _%> + user: user[0], + <%_ } else { _%> + <%= relationship.relationshipName %>Id: <%= relationship.relationshipName %>.id, + <%_ } _%> + <%_ } _%> + <%_ if (oneToOneRelationship && listPage) { _%> + <%= oneToOneRelationship.relationshipName %>Id2: <%= oneToOneRelationship.relationshipName %>.id, + <%_ } _%> + } +} + +export async function add<%= entityAngularName %>( +page +<%_ + if (containsBinaryField) { +_%>, binaryData<%_ } _%><%_ + if (containsBinaryField || containsRelationshipField) { +_%>,<%_ } _%><%_ + let fieldIndex = 0; + const fieldLength = relationshipFields.length; + for (const relationship of relationshipFields) { + if (relationship.otherEntityUser) { +_%>user<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } else { +_%><%= relationship.relationshipName %>Id<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } + fieldIndex++; + } +_%>) { + return await save(page, '<%= servicesApiPrefix %>api/<%= entityApiUrl %>', { +<%_ for (field of fields.filter(field => !field.id)) { + const fieldValue = field.generateFakeData('cypress'); + if (fieldValue === undefined) { + warning(`Error generating a value for field ${field.fieldName}`); + } +_%> + <%_ if (field.fieldTypeBoolean) { _%> + <%= field.fieldName %>: true, + <%_ } else if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + <%= field.fieldName %>: binaryData, + <%= field.fieldName %>ContentType: 'image/png', + <%_ } else if (field.fieldTypeString || field.fieldTypeUUID) { _%> + <%= field.fieldName %>: '<%= fieldValue %>', + <%_ } else if (field.fieldTypeLocalDate) { _%> + <%= field.fieldName %>: '<%= fieldValue %>' , + <%_ } else if (field.fieldTypeTimed) { _%> + <%= field.fieldName %>: '<%= fieldValue %>:00.000Z' , + <%_ } else if (field.fieldTypeDuration) { _%> + <%= field.fieldName %>: 'PT0.000052484S', + <%_ } else if (field.fieldTypeNumeric) { _%> + <%= field.fieldName %>: <%= fieldValue %>, + <%_ } else { _%> + <%= field.fieldName %>: '<%= fieldValue %>', + <%_ } _%> +<%_ } _%> +<%_ for (const relationship of relationshipFields) { + if (relationship.otherEntityUser) { +_%> + user: user, +<%_ + } else { + if (relationship.relationshipManyToMany) { +_%> + <%= relationship.relationshipNamePlural %>: [{ id: <%= relationship.relationshipName %>Id }], +<%_ + } else { +_%> + <%= relationship.relationshipName %>: { id: <%= relationship.relationshipName %>Id }, +<%_ + } + } + } +_%> + }) +} + +<%_ if (!paginationNo || searchEngineAny) { _%> +export async function add<%= entityAngularName %>2( + page + <%_ if (containsBinaryField) {_%> + , binaryData<%_ } _%><%_ + if (containsBinaryField && containsRelationshipField) { +_%>,<%_ } _%><%_ + let fieldIndex = 0; + const fieldLength = relationshipFields.length; + for (const relationship of relationshipFields) { + if (relationship.otherEntityUser) { +_%>user<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } else { +_%><%= relationship.relationshipName %>Id<%_ if (fieldIndex !== (fieldLength - 1)) { _%>,<%_ } _%><%_ + } + fieldIndex++; + } +_%>) { + return await save(page, '<%= servicesApiPrefix %>api/<%= entityApiUrl %>', { +<%_ + for (field of fields.filter(field => !field.id)) { + const fieldValue = field.generateFakeData('cypress'); + if (fieldValue === undefined) { + warning(`Error generating a value for field ${field.fieldName}`); + } +_%> + <%_ if (field.fieldTypeBoolean) { _%> + <%= field.fieldName %>: true, + <%_ } else if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + <%= field.fieldName %>: binaryData, + <%= field.fieldName %>ContentType: 'image/png', + <%_ } else if (field.fieldTypeString || field.fieldTypeUUID) { _%> + <%= field.fieldName %>: '<%= fieldValue %>', + <%_ } else if (field.fieldTypeLocalDate) { _%> + <%= field.fieldName %>: '<%= fieldValue %>' , + <%_ } else if (field.fieldTypeTimed) { _%> + <%= field.fieldName %>: '<%= fieldValue %>:00.000Z' , + <%_ } else if (field.fieldTypeDuration) { _%> + <%= field.fieldName %>: 'PT0.000052484S', + <%_ } else if (field.fieldTypeNumeric) { _%> + <%= field.fieldName %>: <%= fieldValue %>, + <%_ } else { _%> + <%= field.fieldName %>: '<%= fieldValue %>', + <%_ } _%> + <%_ } _%> +<%_ for (const relationship of relationshipFields) { + if (relationship.otherEntityUser) { +_%> + user: user, +<%_ + } else { + if (relationship.relationshipManyToMany) { +_%> + <%= relationship.relationshipNamePlural %>: [{ id: <%= relationship.relationshipName %>Id }], + <%_ + } else { + _%> + <%= relationship.relationshipName %>: { id: <%= relationship.relationshipName %>Id }, + <%_ + } + } + } +_%> + }) +} +<%_ + } +_%> diff --git a/generators/client/templates/playwright/utils/test-utils.js.ejs b/generators/client/templates/playwright/utils/test-utils.js.ejs new file mode 100644 index 000000000..5b3bcab8b --- /dev/null +++ b/generators/client/templates/playwright/utils/test-utils.js.ejs @@ -0,0 +1,277 @@ +import { expect } from '@playwright/test' +import fs from 'node:fs'; +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +export function unregisterServiceWorkers() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker + .getRegistrations() + .then(registrations => registrations.forEach(reg => reg.unregister())) + } +} + +export function getByName(locator, selector) { + return locator.locator(`[name=${selector}]`); +} + +<%_ if (authenticationType === 'session') { _%> +export async function loginByApi(page, username, password) { + const response = await page.request.get(`/api/authenticate`) + expect(response.status()).toBe(200) + + const cookies = await page.context().cookies() + const csrfCookie = cookies.find(cookie => cookie.name === 'XSRF-TOKEN') + expect(csrfCookie).toHaveProperty('value') + + const loginResponse = await page.request.post('/api/authentication', { + form: { + username, + password, + 'remember-me': true, + }, + headers: { + 'X-XSRF-TOKEN': csrfCookie.value, + }, + }) + expect(loginResponse.status()).toBe(200) + + const accountResponse = await page.request.get(`/api/account`) + expect(accountResponse.status()).toBe(200) +} +<%_ } else if (authenticationType === 'jwt') { _%> +export async function loginByApi(page, username, password) { + const response = await page.request.post(`/api/authenticate`, { + data: { + username, + password, + 'remember-me': false, + }, + }) + + expect(response.status()).toBe(200) + const body = await response.json() + + await page.evaluate(token => { + localStorage.setItem('authToken', token) + }, body.id_token) + + const accountResponse = await page.request.get(`/api/account`, { + headers: { + Authorization: `Bearer ${body.id_token}`, + }, + }) + expect(accountResponse.status()).toBe(200) +} + +export async function save(page, url, body, status = 201) { + const token = await page.evaluate(() => { + return localStorage.getItem('authToken') || sessionStorage.getItem('authToken') + }) + + const response = await page.request.post(url, { + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + data: body, + }) + + expect(response.status()).toBe(status) + + return await response.json() +} + +export async function deleteRequest(page, url, lenient = true) { + const token = await page.evaluate(() => { + return localStorage.getItem('authToken') || sessionStorage.getItem('authToken') + }) + + const response = await page.request.delete(url, { + headers: { + Authorization: `Bearer ${token}`, + }, + failOnStatusCode: !lenient, + }) + + if (!lenient) { + expect(response.status()).toBe(204) + } +} + +export async function getById(page, url, status = 200) { + const token = await page.evaluate(() => { + return localStorage.getItem('authToken') || sessionStorage.getItem('authToken') + }) + + const response = await page.request.get(url, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + expect(response.status()).toBe(status) + + return await response.json() +} + +<%_ } else { _%> +export async function loginByApi(page, username, password) { + const response = await page.request.get('/oauth2/authorization/oidc', { + maxRedirects: 0, + }) + + const location = response.headers()['location'] + const url = new URL(location) + const origin = url.origin + + if (origin.includes('okta')) { + await oktaLogin(page, location, origin, username, password) + } else if (origin.includes('auth0')) { + await auth0Login(page, origin, username, password) + } else { + await keycloakLogin(page, username, password) + } + + const accountResponse = await page.request.get('api/account', { + failOnStatusCode: false, + }) + expect(accountResponse.status()).toBe(200) +} + +async function auth0Login(page, origin, username, password) { + const response = await page.request.get('/oauth2/authorization/oidc') + const html = await response.text() + + const stateMatch = html.match(/name="state" value="([^"]+)"/) + const state = stateMatch ? stateMatch[1] : '' + + await page.request.post(`${origin}/u/login`, { + form: { + state, + action: 'default', + username, + password, + }, + }) + + await page.request.get('/oauth2/authorization/oidc') + await page.goto('/') +} + +async function keycloakLogin(page, username, password) { + const response = await page.request.get('/oauth2/authorization/oidc') + const html = await response.text() + + const formUrlMatch = html.match(/]+action="([^"]+)"/) + const formUrl = formUrlMatch ? formUrlMatch[1] : '' + + await page.request.post(formUrl, { + form: { + username, + password, + }, + maxRedirects: 0, + }) + + await page.request.get('/oauth2/authorization/oidc') +} + +async function oktaLogin(page, authorizeUrl, origin, username, password) { + const tokenResponse = await page.request.post(`${origin}/api/v1/authn`, { + data: { + username, + password, + }, + maxRedirects: 0, + }) + + const sessionToken = (await tokenResponse.json()).sessionToken + + await page.request.get(`${authorizeUrl}&sessionToken=${sessionToken}`) +} + +export async function logoutByApi(page) { + const cookies = await page.context().cookies() + const csrfCookie = cookies.find(cookie => cookie.name === 'XSRF-TOKEN') + const origin = new URL(page.url()).origin + + const logoutResponse = await page.request.post('api/logout', { + headers: { + 'X-XSRF-TOKEN': csrfCookie.value, + origin: origin, + }, + }) + expect(logoutResponse.status()).toBe(200) + + const responseBody = await logoutResponse.json() + const finalLogoutResponse = await page.request.get(responseBody.logoutUrl) + expect(finalLogoutResponse.status()).toBe(200) + await page.goto('/') +} + +<%_ } if (authenticationType === 'session' || authenticationType === 'oauth2') { _%> +export async function save(page, url, body, status = 201) { + const cookies = await page.context().cookies() + const csrfCookie = cookies.find(cookie => cookie.name === 'XSRF-TOKEN') + + const response = await page.request.post(url, { + headers: { + 'X-XSRF-TOKEN': csrfCookie.value, + 'Content-Type': 'application/json', + }, + data: body, + }) + + expect(response.status()).toBe(status) + + return await response.json() +} + +export async function deleteRequest(page, url, lenient = true) { + const cookies = await page.context().cookies() + const csrfCookie = cookies.find(cookie => cookie.name === 'XSRF-TOKEN') + + const response = await page.request.delete(url, { + headers: { + 'X-XSRF-TOKEN': csrfCookie.value, + }, + failOnStatusCode: !lenient, + }) + + if (!lenient) { + expect(response.status()).toBe(204) + } +} + +export async function getById(page, url, status = 200) { + const cookies = await page.context().cookies() + const csrfCookie = cookies.find(cookie => cookie.name === 'XSRF-TOKEN') + + const response = await page.request.get(url, { + headers: { + 'X-XSRF-TOKEN': csrfCookie.value, + }, + }) + + expect(response.status()).toBe(status) + + return await response.json() +} + +<%_ } _%> +export async function setFileInput(locator, testFile, mimeType) { + const testFilePath = path.join(__dirname, '../fixtures', testFile) + const fileContent = fs.readFileSync(testFilePath) + + const file = { + name: path.basename(testFile), + mimeType: mimeType, + buffer: fileContent, + } + + await locator.setInputFiles([file]) +} diff --git a/generators/husky/templates/.lintstagedrc.cjs.ejs b/generators/husky/templates/.lintstagedrc.cjs.ejs index 363051c5a..e43ad034a 100644 --- a/generators/husky/templates/.lintstagedrc.cjs.ejs +++ b/generators/husky/templates/.lintstagedrc.cjs.ejs @@ -2,7 +2,7 @@ module.exports = { <%_ if (skipClient) { _%> '{,**/}*.{<%= prettierExtensions %>}': ['prettier --write'], <%_ } else { _%> - '{,src/**/,cypress/**/}*.{md,json,js,svelte,css,html,yml,java}': ['npm run format'], + '{,src/**/,<%= this.blueprintConfig.testFramework %>/**/}*.{md,json,js,svelte,css,html,yml,java}': ['npm run format'], '{src/**/}*.{js,svelte}': ['npm run lint'], '{,src/**/}*.{js,svelte}': ['npm run check'], <%_ } _%> From cb8721d5f285a01b1c7c8c52139ee4a221eb8201 Mon Sep 17 00:00:00 2001 From: Pixel998 Date: Sat, 1 Feb 2025 13:13:14 +0300 Subject: [PATCH 2/4] fix ci --- .github/workflows/applications.yml | 30 +++++++++---------- .../client/templates/eslint.config.js.ejs | 9 ++++-- .../account/change-password.spec.js.ejs | 2 +- .../integration/account/register.spec.js.ejs | 2 +- .../account/reset/init-password.spec.js.ejs | 2 +- .../integration/account/settings.spec.js.ejs | 2 +- .../integration/admin/gateway.spec.js.ejs | 2 +- .../integration/admin/logger.spec.js.ejs | 2 +- .../user-management/user-create.spec.js.ejs | 4 +-- .../user-management/user-delete.spec.js.ejs | 2 +- .../user-management/user-list.spec.js.ejs | 2 +- .../user-management/user-update.spec.js.ejs | 2 +- .../user-management/user-view.spec.js.ejs | 2 +- .../entities/entity/entity-create.spec.js.ejs | 2 +- .../entities/entity/entity-delete.spec.js.ejs | 2 +- .../entities/entity/entity-list.spec.js.ejs | 2 +- .../entities/entity/entity-update.spec.js.ejs | 2 +- .../entities/entity/entity-view.spec.js.ejs | 2 +- .../playwright/integration/footer.spec.js.ejs | 2 +- .../playwright/integration/home.spec.js.ejs | 2 +- .../playwright/integration/login.spec.js.ejs | 2 +- .../playwright/integration/navbar.spec.js.ejs | 12 ++++---- .../playwright/integration/routes.spec.js.ejs | 7 +++-- .../utils/entities/entity-util.js.ejs | 8 ++--- .../playwright/utils/test-utils.js.ejs | 15 ++++++---- .../client/templates/vite.config.js.ejs | 3 +- .../client/templates/vitest/package.json | 1 + 27 files changed, 66 insertions(+), 59 deletions(-) diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index db487408e..c6b4b37df 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -13,7 +13,7 @@ jobs: name: Prepare JHipster Svelte Image runs-on: ubuntu-latest concurrency: - group: apps-dev-${{ matrix.apps }}-${{ github.head_ref || github.sha }} + group: apps-dev-${{ matrix.apps }}-${{ matrix.test_framework }}-${{ github.head_ref || github.sha }} cancel-in-progress: true timeout-minutes: 5 steps: @@ -31,9 +31,9 @@ jobs: retention-days: 1 development-profile-builds: needs: svelte-image - name: ${{ matrix.apps }} + name: ${{ matrix.apps }} (${{ matrix.test_framework }}) concurrency: - group: apps-dev-${{ matrix.apps }}-${{ github.head_ref || github.sha }} + group: apps-dev-${{ matrix.apps }}-${{ matrix.test_framework }}-${{ github.head_ref || github.sha }} cancel-in-progress: true runs-on: ${{ matrix.os }} timeout-minutes: 15 @@ -146,7 +146,7 @@ jobs: cd "$HOME/svelte-app" docker run -d --rm -v $PWD:/app -v $HOME/.m2:/home/jsvelte/.m2 -p8080:8080 --entrypoint ./mvnw jhipster/jhipster-svelte:$GITHUB_SHA -DskipTests -Dspring-boot.run.jvmArguments="-Dspring.docker.compose.enabled=false" timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false - ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' || '' }} npm run e2e:ci - name: Gradle - E2E tests if: contains(matrix.apps, 'gradle') @@ -155,12 +155,12 @@ jobs: docker run --rm -v $PWD:/app -v $HOME/.gradle:/home/jsvelte/.gradle --entrypoint ./gradlew jhipster/jhipster-svelte:$GITHUB_SHA clean docker run -d --rm -v $PWD:/app -v $HOME/.gradle:/home/jsvelte/.gradle -p8080:8080 --entrypoint ./gradlew jhipster/jhipster-svelte:$GITHUB_SHA -x test timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false - ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' || '' }} npm run e2e:ci development-profile-non-docker-builds: - name: ${{ matrix.apps }} + name: ${{ matrix.apps }} (${{ matrix.test_framework }}) concurrency: - group: apps-dev-${{ matrix.apps }}-${{ github.head_ref || github.sha }} + group: apps-dev-${{ matrix.apps }}-${{ matrix.test_framework }}-${{ github.head_ref || github.sha }} cancel-in-progress: true runs-on: ${{ matrix.os }} timeout-minutes: 20 @@ -262,7 +262,7 @@ jobs: cd "$HOME/svelte-app" ./mvnw -svelte:$GITHUB_SHA -DskipTests & timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false - ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' || '' }} npm run e2e:ci - name: Gradle - E2E tests if: contains(matrix.apps, 'gradle') @@ -270,14 +270,14 @@ jobs: cd "$HOME/svelte-app" ./gradlew -x test & timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false - ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' || '' }} npm run e2e:ci production-profile-builds: needs: svelte-image - name: ${{ matrix.apps }} + name: ${{ matrix.apps }} (${{ matrix.test_framework }}) concurrency: - group: apps-prod-${{ matrix.apps }}-${{ github.head_ref || github.sha }} + group: apps-prod-${{ matrix.apps }}-${{ matrix.test_framework }}-${{ github.head_ref || github.sha }} cancel-in-progress: true if: github.event.action != 'closed' runs-on: ${{ matrix.os }} @@ -416,13 +416,13 @@ jobs: sudo echo "127.0.0.1 keycloak" | sudo tee -a /etc/hosts docker compose -f src/main/docker/app.yml up -d timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false - ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' || '' }} npm run e2e:ci microservices-production-profile-builds: needs: svelte-image - name: ${{ matrix.apps }} + name: ${{ matrix.apps }} (${{ matrix.test_framework }}) concurrency: - group: ms-apps-prod-${{ matrix.apps }}-${{ github.head_ref || github.sha }} + group: ms-apps-prod-${{ matrix.apps }}-${{ matrix.test_framework }}-${{ github.head_ref || github.sha }} cancel-in-progress: true if: github.event.action != 'closed' runs-on: ${{ matrix.os }} @@ -582,7 +582,7 @@ jobs: docker compose up -d timeout 180 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/authenticate)" != "200" ]]; do sleep 5; done' || false cd "$HOME/svelte-ms/gateway" - ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' }} + ${{ matrix.test_framework == 'playwright' && 'npx playwright install --with-deps' || '' }} # npm run e2e:ci lighthouse-job: needs: svelte-image diff --git a/generators/client/templates/eslint.config.js.ejs b/generators/client/templates/eslint.config.js.ejs index 840fcdb92..38cf3d9b6 100644 --- a/generators/client/templates/eslint.config.js.ejs +++ b/generators/client/templates/eslint.config.js.ejs @@ -2,7 +2,10 @@ import svelte from "eslint-plugin-svelte"; <%_ if (this.testFramework === 'cypress') { _%> import cypress from "eslint-plugin-cypress"; <%_ } else { _%> -import playwright from 'eslint-plugin-playwright' +import playwright from 'eslint-plugin-playwright'; +<%_ } _%> +<%_ if (!this.blueprintConfig.jest) { _%> +import vitest from '@vitest/eslint-plugin'; <%_ } _%> import jestDom from "eslint-plugin-jest-dom"; import globals from "globals"; @@ -54,9 +57,9 @@ export default [{ ...cypress.environments.globals.globals, <%_ } _%> <%_ if (this.blueprintConfig.jest) { _%> - "jest": true, + ...globals.jest, <%_ } else { _%> - vi: true, + ...vitest.environments.env.globals, <%_ } _%> }, diff --git a/generators/client/templates/playwright/integration/account/change-password.spec.js.ejs b/generators/client/templates/playwright/integration/account/change-password.spec.js.ejs index 38bf8daa5..421852677 100644 --- a/generators/client/templates/playwright/integration/account/change-password.spec.js.ejs +++ b/generators/client/templates/playwright/integration/account/change-password.spec.js.ejs @@ -5,8 +5,8 @@ test.describe('Change user password', () => { test.use({ storageState: 'playwright/.auth/admin.json' }) test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/account/password') + await unregisterServiceWorkers(page) }) test('should greet with Change password title', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/account/register.spec.js.ejs b/generators/client/templates/playwright/integration/account/register.spec.js.ejs index 9b7f5c4f0..0104ef21a 100644 --- a/generators/client/templates/playwright/integration/account/register.spec.js.ejs +++ b/generators/client/templates/playwright/integration/account/register.spec.js.ejs @@ -3,8 +3,8 @@ import { unregisterServiceWorkers } from '../../utils/test-utils' test.describe('Register User', () => { test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/account/register') + await unregisterServiceWorkers(page) }) test('should greet with Create user title', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/account/reset/init-password.spec.js.ejs b/generators/client/templates/playwright/integration/account/reset/init-password.spec.js.ejs index 037c9be9b..f318c4fba 100644 --- a/generators/client/templates/playwright/integration/account/reset/init-password.spec.js.ejs +++ b/generators/client/templates/playwright/integration/account/reset/init-password.spec.js.ejs @@ -3,8 +3,8 @@ import { unregisterServiceWorkers } from '../../../utils/test-utils' test.describe('Forgot password', () => { test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/account/reset/init') + await unregisterServiceWorkers(page) }) test('should greet with forgot password title', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/account/settings.spec.js.ejs b/generators/client/templates/playwright/integration/account/settings.spec.js.ejs index 7a646cd62..6c339edaa 100644 --- a/generators/client/templates/playwright/integration/account/settings.spec.js.ejs +++ b/generators/client/templates/playwright/integration/account/settings.spec.js.ejs @@ -5,8 +5,8 @@ test.describe('User Settings', () => { test.use({ storageState: 'playwright/.auth/admin.json' }) test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/account/settings') + await unregisterServiceWorkers(page) }) test('should greet with User Settings title', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/admin/gateway.spec.js.ejs b/generators/client/templates/playwright/integration/admin/gateway.spec.js.ejs index d1e90f5f0..105c16ba5 100644 --- a/generators/client/templates/playwright/integration/admin/gateway.spec.js.ejs +++ b/generators/client/templates/playwright/integration/admin/gateway.spec.js.ejs @@ -5,8 +5,8 @@ test.describe('Gateway page', () => { test.use({ storageState: 'playwright/.auth/admin.json' }) test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/admin/gateway') + await unregisterServiceWorkers(page) }) test('should greet with Gateway page title', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/admin/logger.spec.js.ejs b/generators/client/templates/playwright/integration/admin/logger.spec.js.ejs index 39bb0ed00..26d209642 100644 --- a/generators/client/templates/playwright/integration/admin/logger.spec.js.ejs +++ b/generators/client/templates/playwright/integration/admin/logger.spec.js.ejs @@ -5,8 +5,8 @@ test.describe('Loggers page', () => { test.use({ storageState: 'playwright/.auth/admin.json' }) test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/admin/logger') + await unregisterServiceWorkers(page) }) test('should greet with loggers page title and filter control', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-create.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-create.spec.js.ejs index 1ee45af69..0391b33ec 100644 --- a/generators/client/templates/playwright/integration/admin/user-management/user-create.spec.js.ejs +++ b/generators/client/templates/playwright/integration/admin/user-management/user-create.spec.js.ejs @@ -5,8 +5,8 @@ test.describe('Create user page', () => { test.use({ storageState: 'playwright/.auth/admin.json' }) test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto(`/admin/user-management/new`) + await unregisterServiceWorkers(page) }) test('should greet with Create user title', async ({ page }) => { @@ -75,9 +75,9 @@ test.describe('Create user page', () => { let randomUser test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() randomUser = `test${new Date().getTime()}` await page.goto(`/admin/user-management/new`) + await unregisterServiceWorkers(page) }) test.afterEach(async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs index 7aa7ccffb..41330a4ed 100644 --- a/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs +++ b/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs @@ -7,7 +7,6 @@ test.describe('User delete dialog page', () => { let randomUser test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() randomUser = `test${new Date().getTime()}` await save(page, 'api/admin/users', { @@ -20,6 +19,7 @@ test.describe('User delete dialog page', () => { }) await page.goto('/admin/user-management') + await unregisterServiceWorkers(page) const row = page.getByTestId('userMgmtTable').getByRole('row').filter({ hasText: randomUser }) diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-list.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-list.spec.js.ejs index 8b9d02dfe..540e3e9cb 100644 --- a/generators/client/templates/playwright/integration/admin/user-management/user-list.spec.js.ejs +++ b/generators/client/templates/playwright/integration/admin/user-management/user-list.spec.js.ejs @@ -5,8 +5,8 @@ test.describe('User Management list page', () => { test.use({ storageState: 'playwright/.auth/admin.json' }) test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/admin/user-management') + await unregisterServiceWorkers(page) }) test('should greet with users page title', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs index 80aa6eca8..6785febdf 100644 --- a/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs +++ b/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs @@ -7,7 +7,6 @@ test.describe('Update user page', () => { let randomUser test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() randomUser = `test${new Date().getTime()}` const response = await save(page, 'api/admin/users', { login: randomUser, @@ -18,6 +17,7 @@ test.describe('Update user page', () => { authorities: ['ROLE_USER'], }) await page.goto(`/admin/user-management/${response.login}/edit`) + await unregisterServiceWorkers(page) }) test.afterEach(async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs index fef4ed96a..dfeb5e59b 100644 --- a/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs +++ b/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs @@ -7,7 +7,6 @@ test.describe('User view details page', () => { let randomUser test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() randomUser = `test${new Date().getTime()}` const response = await save(page, 'api/admin/users', { login: randomUser, @@ -18,6 +17,7 @@ test.describe('User view details page', () => { authorities: ['ROLE_USER'], }) await page.goto(`/admin/user-management/${response.login}/view`) + await unregisterServiceWorkers(page) }) test.afterEach(async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs index 3da729224..20a9f182c 100644 --- a/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs +++ b/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs @@ -26,8 +26,8 @@ test.describe('Create <%= entityInstance %> page', () => { test.use({ storageState: 'playwright/.auth/admin.json' }) test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/entities/<%= entityFolderName %>/new') + await unregisterServiceWorkers(page) }) test('should greet with Create <%= entityInstance %> title', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs index 484196e05..2be1f1ecb 100644 --- a/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs +++ b/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs @@ -61,8 +61,8 @@ _%> }) test.beforeEach(async () => { - unregisterServiceWorkers() await page.goto('/entities/<%= entityFolderName %>') + await unregisterServiceWorkers(page) const rows = page.getByTestId('<%= entityInstance %>Table').getByRole('row') const targetRow = rows.filter({ hasText: dynamicId }) diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs index 12423f733..c6fb636b7 100644 --- a/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs +++ b/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs @@ -96,8 +96,8 @@ _%> }) test.beforeEach(async () => { - unregisterServiceWorkers() await page.goto('/entities/<%= entityFolderName %>') + await unregisterServiceWorkers(page) }) test.afterAll(async () => { diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs index 8802c86bc..7d7e02934 100644 --- a/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs +++ b/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs @@ -78,8 +78,8 @@ _%> }) test.beforeEach(async () => { - unregisterServiceWorkers() await page.goto(`/entities/<%= entityFolderName %>/${dynamicId}/edit`) + await unregisterServiceWorkers(page) }) test.afterAll(async () => { diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs index 4d8c08dca..d3a2e5050 100644 --- a/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs +++ b/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs @@ -66,8 +66,8 @@ _%> }) test.beforeEach(async () => { - unregisterServiceWorkers() await page.goto(`/entities/<%= entityFolderName %>/${dynamicId}/view`) + await unregisterServiceWorkers(page) }) test.afterAll(async () => { diff --git a/generators/client/templates/playwright/integration/footer.spec.js.ejs b/generators/client/templates/playwright/integration/footer.spec.js.ejs index 640d51da7..87cc6000c 100644 --- a/generators/client/templates/playwright/integration/footer.spec.js.ejs +++ b/generators/client/templates/playwright/integration/footer.spec.js.ejs @@ -3,8 +3,8 @@ import { unregisterServiceWorkers } from '../utils/test-utils' test.describe('Footer', () => { test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/') + await unregisterServiceWorkers(page) }) test('should display copyright message', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/home.spec.js.ejs b/generators/client/templates/playwright/integration/home.spec.js.ejs index 720e592f6..2f4344e6b 100644 --- a/generators/client/templates/playwright/integration/home.spec.js.ejs +++ b/generators/client/templates/playwright/integration/home.spec.js.ejs @@ -3,8 +3,8 @@ import { unregisterServiceWorkers } from '../utils/test-utils' test.describe('Home page', () => { test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/') + await unregisterServiceWorkers(page) }) test('should greet with welcome title', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/login.spec.js.ejs b/generators/client/templates/playwright/integration/login.spec.js.ejs index 6e1df12ad..1a3357388 100644 --- a/generators/client/templates/playwright/integration/login.spec.js.ejs +++ b/generators/client/templates/playwright/integration/login.spec.js.ejs @@ -3,8 +3,8 @@ import { unregisterServiceWorkers } from '../utils/test-utils' test.describe('User login', () => { test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/login') + await unregisterServiceWorkers(page) }) test('should greet with Sign in', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/navbar.spec.js.ejs b/generators/client/templates/playwright/integration/navbar.spec.js.ejs index c1428ae9d..73abcab1b 100644 --- a/generators/client/templates/playwright/integration/navbar.spec.js.ejs +++ b/generators/client/templates/playwright/integration/navbar.spec.js.ejs @@ -3,8 +3,8 @@ import { loginByApi, unregisterServiceWorkers } from '../utils/test-utils' test.describe('Navbar', () => { test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/') + await unregisterServiceWorkers(page) }) test.describe('unauthenticated user', () => { @@ -42,9 +42,9 @@ test.describe('Navbar', () => { test.describe('authenticated user', () => { test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() - await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) await page.goto('/') + await unregisterServiceWorkers(page) + await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) }) <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> @@ -143,8 +143,8 @@ test.describe('Navbar', () => { test.use({ storageState: 'playwright/.auth/user.json' }) test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/') + await unregisterServiceWorkers(page) }) test('should not display administrator menu', async ({ page }) => { @@ -156,9 +156,9 @@ test.describe('Navbar', () => { <%_ if (authenticationType === 'oauth2') { _%> test.describe('Logout authenticated user', () => { test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() - await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) await page.goto('/') + await unregisterServiceWorkers(page) + await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) }) test('should logout user', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/routes.spec.js.ejs b/generators/client/templates/playwright/integration/routes.spec.js.ejs index ee34a6ff2..5d378d122 100644 --- a/generators/client/templates/playwright/integration/routes.spec.js.ejs +++ b/generators/client/templates/playwright/integration/routes.spec.js.ejs @@ -2,8 +2,9 @@ import { expect, test } from '@playwright/test' import { unregisterServiceWorkers } from '../utils/test-utils' test.describe('Routes', () => { - test.beforeEach(async () => { - unregisterServiceWorkers() + test.beforeEach(async ({ page }) => { + await page.goto('/') + await unregisterServiceWorkers(page) }) test.describe('unauthenticated user', () => { @@ -50,8 +51,8 @@ test.describe('Routes', () => { test.use({ storageState: 'playwright/.auth/admin.json' }) test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/') + await unregisterServiceWorkers(page) }) test('should not allow navigation to login page', async ({ page }) => { diff --git a/generators/client/templates/playwright/utils/entities/entity-util.js.ejs b/generators/client/templates/playwright/utils/entities/entity-util.js.ejs index 029b0d8b0..630b7fdc5 100644 --- a/generators/client/templates/playwright/utils/entities/entity-util.js.ejs +++ b/generators/client/templates/playwright/utils/entities/entity-util.js.ejs @@ -108,8 +108,7 @@ export async function prepare<%= entityAngularName %>PrerequisiteData(page, list } export async function add<%= entityAngularName %>( -page -<%_ +page<%_ if (containsBinaryField) { _%>, binaryData<%_ } _%><%_ if (containsBinaryField || containsRelationshipField) { @@ -174,10 +173,9 @@ _%> <%_ if (!paginationNo || searchEngineAny) { _%> export async function add<%= entityAngularName %>2( - page - <%_ if (containsBinaryField) {_%> +page<%_ if (containsBinaryField) {_%> , binaryData<%_ } _%><%_ - if (containsBinaryField && containsRelationshipField) { + if (containsBinaryField || containsRelationshipField) { _%>,<%_ } _%><%_ let fieldIndex = 0; const fieldLength = relationshipFields.length; diff --git a/generators/client/templates/playwright/utils/test-utils.js.ejs b/generators/client/templates/playwright/utils/test-utils.js.ejs index 5b3bcab8b..dfb6f2227 100644 --- a/generators/client/templates/playwright/utils/test-utils.js.ejs +++ b/generators/client/templates/playwright/utils/test-utils.js.ejs @@ -6,12 +6,15 @@ import { fileURLToPath } from 'node:url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) -export function unregisterServiceWorkers() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker - .getRegistrations() - .then(registrations => registrations.forEach(reg => reg.unregister())) - } +export async function unregisterServiceWorkers(page) { + await page.evaluate(async () => { + if ('serviceWorker' in navigator) { + const registrations = await navigator.serviceWorker.getRegistrations() + await Promise.all( + registrations.map(registration => registration.unregister()) + ); + } + }); } export function getByName(locator, selector) { diff --git a/generators/client/templates/vite.config.js.ejs b/generators/client/templates/vite.config.js.ejs index d1635c371..ce2ac05a2 100644 --- a/generators/client/templates/vite.config.js.ejs +++ b/generators/client/templates/vite.config.js.ejs @@ -68,7 +68,8 @@ export default defineConfig({ reporter: ['text', 'html', 'lcov'], reportsDirectory: '<%= temporaryDir %>test-results/js/', exclude: ['<%= temporaryDir %>'] - } + }, + exclude: ['**/node_modules/**', '<%= this.testFramework %>/**'], }, <%_ } _%> diff --git a/generators/client/templates/vitest/package.json b/generators/client/templates/vitest/package.json index 98eab4b96..8a1401c39 100644 --- a/generators/client/templates/vitest/package.json +++ b/generators/client/templates/vitest/package.json @@ -6,6 +6,7 @@ }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "3.1.2", + "@vitest/eslint-plugin": "1.1.25", "@vitest/ui": "2.1.2", "@vitest/coverage-v8": "2.1.2", "jsdom": "25.0.1", From f7a25dd2b0cc99433042dbb6a03545733d315b82 Mon Sep 17 00:00:00 2001 From: Pixel998 Date: Sun, 2 Feb 2025 08:54:57 +0300 Subject: [PATCH 3/4] fix ci --- .github/workflows/applications.yml | 4 +- .../integration/account/settings.spec.js.ejs | 6 +-- .../user-management/user-delete.spec.js.ejs | 3 +- .../user-management/user-update.spec.js.ejs | 3 +- .../user-management/user-view.spec.js.ejs | 3 +- .../entities/entity/entity-create.spec.js.ejs | 8 ++-- .../entities/entity/entity-delete.spec.js.ejs | 1 + .../entities/entity/entity-list.spec.js.ejs | 1 + .../entities/entity/entity-update.spec.js.ejs | 9 +++-- .../entities/entity/entity-view.spec.js.ejs | 1 + .../playwright/integration/home.spec.js.ejs | 2 +- .../playwright/integration/navbar.spec.js.ejs | 38 +++++++++---------- .../utils/entities/entity-util.js.ejs | 9 +++-- 13 files changed, 49 insertions(+), 39 deletions(-) diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index c6b4b37df..7d92ddb7f 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -36,7 +36,7 @@ jobs: group: apps-dev-${{ matrix.apps }}-${{ matrix.test_framework }}-${{ github.head_ref || github.sha }} cancel-in-progress: true runs-on: ${{ matrix.os }} - timeout-minutes: 15 + timeout-minutes: 20 strategy: fail-fast: false matrix: @@ -281,7 +281,7 @@ jobs: cancel-in-progress: true if: github.event.action != 'closed' runs-on: ${{ matrix.os }} - timeout-minutes: 15 + timeout-minutes: 20 strategy: fail-fast: false matrix: diff --git a/generators/client/templates/playwright/integration/account/settings.spec.js.ejs b/generators/client/templates/playwright/integration/account/settings.spec.js.ejs index 6c339edaa..b72a9beaa 100644 --- a/generators/client/templates/playwright/integration/account/settings.spec.js.ejs +++ b/generators/client/templates/playwright/integration/account/settings.spec.js.ejs @@ -19,13 +19,13 @@ test.describe('User Settings', () => { const form = page.getByTestId('settingsForm') const firstName = form.getByRole('textbox', { name: 'first name' }) - await expect(firstName).toHaveValue(<%_ if (databaseTypeSql) { _%>'Admin'<%_ } else { _%>'admin'<%_ }_%>) + await expect(firstName).toContain(<%_ if (databaseTypeSql) { _%>'Admin'<%_ } else { _%>'admin'<%_ }_%>) const lastName = form.getByRole('textbox', { name: 'last name' }) - await expect(lastName).toHaveValue('Admin') + await expect(lastName).toContain('Admin') const email = form.getByRole('textbox', { name: 'email' }) - await expect(email).toHaveValue('admin@localhost.org') + await expect(email).toContain('admin@localhost') }) test('should display form control validation messages', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs index 41330a4ed..0369cb679 100644 --- a/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs +++ b/generators/client/templates/playwright/integration/admin/user-management/user-delete.spec.js.ejs @@ -7,6 +7,8 @@ test.describe('User delete dialog page', () => { let randomUser test.beforeEach(async ({ page }) => { + await page.goto('/') + await unregisterServiceWorkers(page) randomUser = `test${new Date().getTime()}` await save(page, 'api/admin/users', { @@ -19,7 +21,6 @@ test.describe('User delete dialog page', () => { }) await page.goto('/admin/user-management') - await unregisterServiceWorkers(page) const row = page.getByTestId('userMgmtTable').getByRole('row').filter({ hasText: randomUser }) diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs index 6785febdf..077fbd894 100644 --- a/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs +++ b/generators/client/templates/playwright/integration/admin/user-management/user-update.spec.js.ejs @@ -7,6 +7,8 @@ test.describe('Update user page', () => { let randomUser test.beforeEach(async ({ page }) => { + await page.goto('/') + await unregisterServiceWorkers(page) randomUser = `test${new Date().getTime()}` const response = await save(page, 'api/admin/users', { login: randomUser, @@ -17,7 +19,6 @@ test.describe('Update user page', () => { authorities: ['ROLE_USER'], }) await page.goto(`/admin/user-management/${response.login}/edit`) - await unregisterServiceWorkers(page) }) test.afterEach(async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs b/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs index dfeb5e59b..f9a2e0be9 100644 --- a/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs +++ b/generators/client/templates/playwright/integration/admin/user-management/user-view.spec.js.ejs @@ -7,6 +7,8 @@ test.describe('User view details page', () => { let randomUser test.beforeEach(async ({ page }) => { + await page.goto('/') + await unregisterServiceWorkers(page) randomUser = `test${new Date().getTime()}` const response = await save(page, 'api/admin/users', { login: randomUser, @@ -17,7 +19,6 @@ test.describe('User view details page', () => { authorities: ['ROLE_USER'], }) await page.goto(`/admin/user-management/${response.login}/view`) - await unregisterServiceWorkers(page) }) test.afterEach(async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs index 20a9f182c..8d01684b2 100644 --- a/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs +++ b/generators/client/templates/playwright/integration/entities/entity/entity-create.spec.js.ejs @@ -80,23 +80,23 @@ _%> <%_ } _%> <%_ if (field.fieldTypeNumeric && !field.fieldValidationRequired) { _%> - await <%= field.fieldName %>.fill('1') + await <%= field.fieldName %>.pressSequentially('1') await <%= field.fieldName %>.clear() - await <%= field.fieldName %>.fill('-') + await <%= field.fieldName %>.pressSequentially('-') await <%= field.fieldName %>.blur() await expect(<%= field.fieldName %>Error).toBeVisible() await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> should be numeric') <%_ } _%> <%_ if (field.fieldTypeNumeric && field.fieldValidationMin) { _%> - await <%= field.fieldName %>.fill('<%= Number(field.fieldValidateRulesMin) - 1 %>') + await <%= field.fieldName %>.pressSequentially('<%= Number(field.fieldValidateRulesMin) - 1 %>') await <%= field.fieldName %>.blur() await expect(<%= field.fieldName %>Error).toBeVisible() await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> should be at least <%= field.fieldValidateRulesMin %>') <%_ } _%> <%_ if (field.fieldTypeNumeric && field.fieldValidationMax) { _%> - await <%= field.fieldName %>.fill('<%= Number(field.fieldValidateRulesMax) + 1 %>') + await <%= field.fieldName %>.pressSequentially('<%= Number(field.fieldValidateRulesMax) + 1 %>') await <%= field.fieldName %>.blur() await expect(<%= field.fieldName %>Error).toBeVisible() await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> cannot be more than <%= field.fieldValidateRulesMax %>') diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs index 2be1f1ecb..d427c48e3 100644 --- a/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs +++ b/generators/client/templates/playwright/integration/entities/entity/entity-delete.spec.js.ejs @@ -30,6 +30,7 @@ _%> test.beforeAll(async ({ browser }) => { page = await browser.newPage() + await page.goto('/') <%_ if (containsRelationshipField || containsBinaryField ) { _%> const data = await prepare<%= entityAngularName %>PrerequisiteData(page) <%_ } _%> diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs index c6fb636b7..65b5b1589 100644 --- a/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs +++ b/generators/client/templates/playwright/integration/entities/entity/entity-list.spec.js.ejs @@ -37,6 +37,7 @@ _%> test.beforeAll(async ({ browser }) => { page = await browser.newPage() + await page.goto('/') <%_ if (containsRelationshipField || containsBinaryField ) { _%> const data = await prepare<%= entityAngularName %>PrerequisiteData(page, <%= !!(!paginationNo || searchEngineAny) %>) <%_ } _%> diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs index 7d7e02934..8aadce517 100644 --- a/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs +++ b/generators/client/templates/playwright/integration/entities/entity/entity-update.spec.js.ejs @@ -47,6 +47,7 @@ _%> test.beforeAll(async ({ browser }) => { page = await browser.newPage() + await page.goto('/') <%_ if (containsRelationshipField || containsBinaryField ) { _%> const data = await prepare<%= entityAngularName %>PrerequisiteData(page) <%_ } _%> @@ -158,23 +159,23 @@ _%> <%_ } _%> <%_ if (field.fieldTypeNumeric && !field.fieldValidationRequired) { _%> - await <%= field.fieldName %>.fill('1') + await <%= field.fieldName %>.pressSequentially('1') await <%= field.fieldName %>.clear() - await <%= field.fieldName %>.fill('-') + await <%= field.fieldName %>.pressSequentially('-') await <%= field.fieldName %>.blur() await expect(<%= field.fieldName %>Error).toBeVisible() await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> should be numeric') <%_ } _%> <%_ if (field.fieldTypeNumeric && field.fieldValidationMin) { _%> - await <%= field.fieldName %>.fill('<%= Number(field.fieldValidateRulesMin) - 1 %>') + await <%= field.fieldName %>.pressSequentially('<%= Number(field.fieldValidateRulesMin) - 1 %>') await <%= field.fieldName %>.blur() await expect(<%= field.fieldName %>Error).toBeVisible() await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> should be at least <%= field.fieldValidateRulesMin %>') <%_ } _%> <%_ if (field.fieldTypeNumeric && field.fieldValidationMax) { _%> - await <%= field.fieldName %>.fill('<%= Number(field.fieldValidateRulesMax) + 1 %>') + await <%= field.fieldName %>.pressSequentially('<%= Number(field.fieldValidateRulesMax) + 1 %>') await <%= field.fieldName %>.blur() await expect(<%= field.fieldName %>Error).toBeVisible() await expect(<%= field.fieldName %>Error).toContainText('<%= field.fieldNameHumanized %> cannot be more than <%= field.fieldValidateRulesMax %>') diff --git a/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs b/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs index d3a2e5050..3126d24e8 100644 --- a/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs +++ b/generators/client/templates/playwright/integration/entities/entity/entity-view.spec.js.ejs @@ -35,6 +35,7 @@ _%> test.beforeAll(async ({ browser }) => { page = await browser.newPage() + await page.goto('/') <%_ if (containsRelationshipField || containsBinaryField ) { _%> const data = await prepare<%= entityAngularName %>PrerequisiteData(page) <%_ } _%> diff --git a/generators/client/templates/playwright/integration/home.spec.js.ejs b/generators/client/templates/playwright/integration/home.spec.js.ejs index 2f4344e6b..d38ce02be 100644 --- a/generators/client/templates/playwright/integration/home.spec.js.ejs +++ b/generators/client/templates/playwright/integration/home.spec.js.ejs @@ -36,8 +36,8 @@ test.describe('Home page', () => { test.use({ storageState: 'playwright/.auth/admin.json' }) test.beforeEach(async ({ page }) => { - unregisterServiceWorkers() await page.goto('/') + await unregisterServiceWorkers(page) }) test('should greet logged in user', async ({ page }) => { diff --git a/generators/client/templates/playwright/integration/navbar.spec.js.ejs b/generators/client/templates/playwright/integration/navbar.spec.js.ejs index 73abcab1b..f98912a88 100644 --- a/generators/client/templates/playwright/integration/navbar.spec.js.ejs +++ b/generators/client/templates/playwright/integration/navbar.spec.js.ejs @@ -42,9 +42,9 @@ test.describe('Navbar', () => { test.describe('authenticated user', () => { test.beforeEach(async ({ page }) => { + await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) await page.goto('/') await unregisterServiceWorkers(page) - await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) }) <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> @@ -65,17 +65,17 @@ test.describe('Navbar', () => { await expect(accountLink).toBeEnabled() await accountLink.click() - <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> - const passwordLink = page.getByTestId('svlChgPwdLink') - await expect(passwordLink).toBeVisible() - await expect(passwordLink).toHaveAttribute('href', '/account/password') - await expect(passwordLink).toContainText('Change Password') - - const settingsLink = page.getByTestId('svlSettingsLink') - await expect(settingsLink).toBeVisible() - await expect(settingsLink).toHaveAttribute('href', '/account/settings') - await expect(settingsLink).toContainText('Settings') - <%_ } _%> + <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> + const passwordLink = page.getByTestId('svlChgPwdLink') + await expect(passwordLink).toBeVisible() + await expect(passwordLink).toHaveAttribute('href', '/account/password') + await expect(passwordLink).toContainText('Change Password') + + const settingsLink = page.getByTestId('svlSettingsLink') + await expect(settingsLink).toBeVisible() + await expect(settingsLink).toHaveAttribute('href', '/account/settings') + await expect(settingsLink).toContainText('Settings') + <%_ } _%> const signoutLink = page.getByTestId('svlSignoutLink') await expect(signoutLink).toBeVisible() @@ -96,12 +96,12 @@ test.describe('Navbar', () => { await expect(loggerLink).toHaveAttribute('href', '/admin/logger') await expect(loggerLink).toContainText('Loggers') - <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> - const userMgmtLink = page.getByTestId('svlUserMgmtLink') - await expect(userMgmtLink).toBeVisible() - await expect(userMgmtLink).toHaveAttribute('href', '/admin/user-management') - await expect(userMgmtLink).toContainText('User Management') - <%_ } _%> + <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> + const userMgmtLink = page.getByTestId('svlUserMgmtLink') + await expect(userMgmtLink).toBeVisible() + await expect(userMgmtLink).toHaveAttribute('href', '/admin/user-management') + await expect(userMgmtLink).toContainText('User Management') + <%_ } _%> }) <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%> @@ -156,9 +156,9 @@ test.describe('Navbar', () => { <%_ if (authenticationType === 'oauth2') { _%> test.describe('Logout authenticated user', () => { test.beforeEach(async ({ page }) => { + await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) await page.goto('/') await unregisterServiceWorkers(page) - await loginByApi(page, process.env.ADMIN_USERNAME, process.env.ADMIN_PASSWORD) }) test('should logout user', async ({ page }) => { diff --git a/generators/client/templates/playwright/utils/entities/entity-util.js.ejs b/generators/client/templates/playwright/utils/entities/entity-util.js.ejs index 630b7fdc5..cd453f927 100644 --- a/generators/client/templates/playwright/utils/entities/entity-util.js.ejs +++ b/generators/client/templates/playwright/utils/entities/entity-util.js.ejs @@ -60,8 +60,10 @@ export async function prepare<%= entityAngularName %>PrerequisiteData(page, list <%_ } } if (oneToOneRelationship) { _%> + + let <%= oneToOneRelationship.relationshipName %>2 if (listPage) { - const <%= oneToOneRelationship.relationshipName %>2 = await save(page, '<%= servicesApiPrefix %>api/<%= oneToOneRelationship.otherEntity.entityApiUrl %>', { + <%= oneToOneRelationship.relationshipName %>2 = await save(page, '<%= servicesApiPrefix %>api/<%= oneToOneRelationship.otherEntity.entityApiUrl %>', { <%_ for (const relationshipField of oneToOneRelationship.otherEntity.fields.filter(field => !field.id)) { const fieldValue = relationshipField.generateFakeData('cypress'); if (fieldValue === undefined) { @@ -90,6 +92,7 @@ export async function prepare<%= entityAngularName %>PrerequisiteData(page, list }) } <%_ } _%> + return { <%_ if (containsBinaryField) { _%> binaryData, @@ -101,8 +104,8 @@ export async function prepare<%= entityAngularName %>PrerequisiteData(page, list <%= relationship.relationshipName %>Id: <%= relationship.relationshipName %>.id, <%_ } _%> <%_ } _%> - <%_ if (oneToOneRelationship && listPage) { _%> - <%= oneToOneRelationship.relationshipName %>Id2: <%= oneToOneRelationship.relationshipName %>.id, + <%_ if (oneToOneRelationship) { _%> + ...(listPage && { <%= oneToOneRelationship.relationshipName %>Id2: <%= oneToOneRelationship.relationshipName %>2.id }), <%_ } _%> } } From 99b0c940e3868f7457111b8b35268f463d24fbf7 Mon Sep 17 00:00:00 2001 From: Pixel998 Date: Sun, 2 Feb 2025 09:51:31 +0300 Subject: [PATCH 4/4] fix ci --- .github/workflows/applications.yml | 2 +- .../playwright/integration/account/settings.spec.js.ejs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index 7d92ddb7f..d2c9c6145 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -647,7 +647,7 @@ jobs: run: | cd "$HOME/svelte-app" cp $TEST_SCRIPTS_PATH/${{ matrix.apps }} app.jdl - docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA import-jdl app.jdl --no-insight --force + docker run --rm -v $PWD:/app -v $HOME/.cache:/home/jsvelte/.cache jhipster/jhipster-svelte:$GITHUB_SHA import-jdl app.jdl --no-insight --force --test-framework cypress rm -rf node_modules/generator-jhipster-svelte - name: Build application docker image run: | diff --git a/generators/client/templates/playwright/integration/account/settings.spec.js.ejs b/generators/client/templates/playwright/integration/account/settings.spec.js.ejs index b72a9beaa..5bf96c33a 100644 --- a/generators/client/templates/playwright/integration/account/settings.spec.js.ejs +++ b/generators/client/templates/playwright/integration/account/settings.spec.js.ejs @@ -19,13 +19,13 @@ test.describe('User Settings', () => { const form = page.getByTestId('settingsForm') const firstName = form.getByRole('textbox', { name: 'first name' }) - await expect(firstName).toContain(<%_ if (databaseTypeSql) { _%>'Admin'<%_ } else { _%>'admin'<%_ }_%>) + await expect(firstName).toHaveValue(<%_ if (databaseTypeSql) { _%>/^Admin/<%_ } else { _%>/^admin/<%_ }_%>) const lastName = form.getByRole('textbox', { name: 'last name' }) - await expect(lastName).toContain('Admin') + await expect(lastName).toHaveValue(/^Admin/) const email = form.getByRole('textbox', { name: 'email' }) - await expect(email).toContain('admin@localhost') + await expect(email).toHaveValue(/^admin@localhost/) }) test('should display form control validation messages', async ({ page }) => {