From b30a40fe0718154fc1d92665fb82c2056ceade43 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Thu, 16 Jan 2025 16:46:30 +0100 Subject: [PATCH 01/50] feat: add Playwright testing framework configuration and tests --- .github/workflows/playwright.yml | 27 ++ .gitignore | 5 + package-lock.json | 68 ++++- package.json | 2 + playwright.config.ts | 79 +++++ tests-examples/demo-todo-app.spec.ts | 437 +++++++++++++++++++++++++++ tests/example.spec.ts | 18 ++ 7 files changed, 632 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/playwright.yml create mode 100644 playwright.config.ts create mode 100644 tests-examples/demo-todo-app.spec.ts create mode 100644 tests/example.spec.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..3eb1314 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index cc7b141..f0960ba 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,8 @@ testem.log # System files .DS_Store Thumbs.db +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package-lock.json b/package-lock.json index 4bfaa2f..9d30a42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,9 @@ "@angular-devkit/build-angular": "^18.2.0", "@angular/cli": "^18.2.0", "@angular/compiler-cli": "^18.2.0", + "@playwright/test": "^1.49.1", "@types/jasmine": "~5.1.0", + "@types/node": "^22.10.7", "jasmine-core": "~5.2.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -3896,6 +3898,21 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", + "dev": true, + "dependencies": { + "playwright": "1.49.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -4452,11 +4469,10 @@ } }, "node_modules/@types/node": { - "version": "22.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", - "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", + "version": "22.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", + "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~6.20.0" } @@ -10819,6 +10835,50 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/playwright": { + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", + "dev": true, + "dependencies": { + "playwright-core": "1.49.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.4.41", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", diff --git a/package.json b/package.json index f1859dd..e5df74a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,9 @@ "@angular-devkit/build-angular": "^18.2.0", "@angular/cli": "^18.2.0", "@angular/compiler-cli": "^18.2.0", + "@playwright/test": "^1.49.1", "@types/jasmine": "~5.1.0", + "@types/node": "^22.10.7", "jasmine-core": "~5.2.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..a05d8b5 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,79 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* 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://127.0.0.1:3000', + + /* 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: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + 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/tests-examples/demo-todo-app.spec.ts b/tests-examples/demo-todo-app.spec.ts new file mode 100644 index 0000000..8641cb5 --- /dev/null +++ b/tests-examples/demo-todo-app.spec.ts @@ -0,0 +1,437 @@ +import { test, expect, type Page } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await page.goto('https://demo.playwright.dev/todomvc'); +}); + +const TODO_ITEMS = [ + 'buy some cheese', + 'feed the cat', + 'book a doctors appointment' +] as const; + +test.describe('New Todo', () => { + test('should allow me to add todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create 1st todo. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Make sure the list only has one todo item. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0] + ]); + + // Create 2nd todo. + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + + // Make sure the list now has two todo items. + await expect(page.getByTestId('todo-title')).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[1] + ]); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); + + test('should clear text input field when an item is added', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create one todo item. + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + // Check that input is empty. + await expect(newTodo).toBeEmpty(); + await checkNumberOfTodosInLocalStorage(page, 1); + }); + + test('should append new items to the bottom of the list', async ({ page }) => { + // Create 3 items. + await createDefaultTodos(page); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + // Check test using different methods. + await expect(page.getByText('3 items left')).toBeVisible(); + await expect(todoCount).toHaveText('3 items left'); + await expect(todoCount).toContainText('3'); + await expect(todoCount).toHaveText(/3/); + + // Check all items in one call. + await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); + await checkNumberOfTodosInLocalStorage(page, 3); + }); +}); + +test.describe('Mark all as completed', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test.afterEach(async ({ page }) => { + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should allow me to mark all items as completed', async ({ page }) => { + // Complete all todos. + await page.getByLabel('Mark all as complete').check(); + + // Ensure all todos have 'completed' class. + await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + }); + + test('should allow me to clear the complete state of all items', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + // Check and then immediately uncheck. + await toggleAll.check(); + await toggleAll.uncheck(); + + // Should be no completed classes. + await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); + }); + + test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { + const toggleAll = page.getByLabel('Mark all as complete'); + await toggleAll.check(); + await expect(toggleAll).toBeChecked(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Uncheck first todo. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').uncheck(); + + // Reuse toggleAll locator and make sure its not checked. + await expect(toggleAll).not.toBeChecked(); + + await firstTodo.getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 3); + + // Assert the toggle all is checked again. + await expect(toggleAll).toBeChecked(); + }); +}); + +test.describe('Item', () => { + + test('should allow me to mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + // Check first item. + const firstTodo = page.getByTestId('todo-item').nth(0); + await firstTodo.getByRole('checkbox').check(); + await expect(firstTodo).toHaveClass('completed'); + + // Check second item. + const secondTodo = page.getByTestId('todo-item').nth(1); + await expect(secondTodo).not.toHaveClass('completed'); + await secondTodo.getByRole('checkbox').check(); + + // Assert completed class. + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).toHaveClass('completed'); + }); + + test('should allow me to un-mark items as complete', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // Create two items. + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const firstTodo = page.getByTestId('todo-item').nth(0); + const secondTodo = page.getByTestId('todo-item').nth(1); + const firstTodoCheckbox = firstTodo.getByRole('checkbox'); + + await firstTodoCheckbox.check(); + await expect(firstTodo).toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await firstTodoCheckbox.uncheck(); + await expect(firstTodo).not.toHaveClass('completed'); + await expect(secondTodo).not.toHaveClass('completed'); + await checkNumberOfCompletedTodosInLocalStorage(page, 0); + }); + + test('should allow me to edit an item', async ({ page }) => { + await createDefaultTodos(page); + + const todoItems = page.getByTestId('todo-item'); + const secondTodo = todoItems.nth(1); + await secondTodo.dblclick(); + await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); + await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); + + // Explicitly assert the new text value. + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2] + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); +}); + +test.describe('Editing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should hide other controls when editing', async ({ page }) => { + const todoItem = page.getByTestId('todo-item').nth(1); + await todoItem.dblclick(); + await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); + await expect(todoItem.locator('label', { + hasText: TODO_ITEMS[1], + })).not.toBeVisible(); + await checkNumberOfTodosInLocalStorage(page, 3); + }); + + test('should save edits on blur', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should trim entered text', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + 'buy some sausages', + TODO_ITEMS[2], + ]); + await checkTodosInLocalStorage(page, 'buy some sausages'); + }); + + test('should remove the item if an empty text string was entered', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); + + await expect(todoItems).toHaveText([ + TODO_ITEMS[0], + TODO_ITEMS[2], + ]); + }); + + test('should cancel edits on escape', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).dblclick(); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); + await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); + await expect(todoItems).toHaveText(TODO_ITEMS); + }); +}); + +test.describe('Counter', () => { + test('should display the current number of todo items', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + // create a todo count locator + const todoCount = page.getByTestId('todo-count') + + await newTodo.fill(TODO_ITEMS[0]); + await newTodo.press('Enter'); + + await expect(todoCount).toContainText('1'); + + await newTodo.fill(TODO_ITEMS[1]); + await newTodo.press('Enter'); + await expect(todoCount).toContainText('2'); + + await checkNumberOfTodosInLocalStorage(page, 2); + }); +}); + +test.describe('Clear completed button', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + }); + + test('should display the correct text', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); + }); + + test('should remove completed items when clicked', async ({ page }) => { + const todoItems = page.getByTestId('todo-item'); + await todoItems.nth(1).getByRole('checkbox').check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(todoItems).toHaveCount(2); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should be hidden when there are no items that are completed', async ({ page }) => { + await page.locator('.todo-list li .toggle').first().check(); + await page.getByRole('button', { name: 'Clear completed' }).click(); + await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); + }); +}); + +test.describe('Persistence', () => { + test('should persist its data', async ({ page }) => { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS.slice(0, 2)) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } + + const todoItems = page.getByTestId('todo-item'); + const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); + await firstTodoCheck.check(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + + // Ensure there is 1 completed item. + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + // Now reload. + await page.reload(); + await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); + await expect(firstTodoCheck).toBeChecked(); + await expect(todoItems).toHaveClass(['completed', '']); + }); +}); + +test.describe('Routing', () => { + test.beforeEach(async ({ page }) => { + await createDefaultTodos(page); + // make sure the app had a chance to save updated todos in storage + // before navigating to a new view, otherwise the items can get lost :( + // in some frameworks like Durandal + await checkTodosInLocalStorage(page, TODO_ITEMS[0]); + }); + + test('should allow me to display active items', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await expect(todoItem).toHaveCount(2); + await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); + }); + + test('should respect the back button', async ({ page }) => { + const todoItem = page.getByTestId('todo-item'); + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + + await test.step('Showing all items', async () => { + await page.getByRole('link', { name: 'All' }).click(); + await expect(todoItem).toHaveCount(3); + }); + + await test.step('Showing active items', async () => { + await page.getByRole('link', { name: 'Active' }).click(); + }); + + await test.step('Showing completed items', async () => { + await page.getByRole('link', { name: 'Completed' }).click(); + }); + + await expect(todoItem).toHaveCount(1); + await page.goBack(); + await expect(todoItem).toHaveCount(2); + await page.goBack(); + await expect(todoItem).toHaveCount(3); + }); + + test('should allow me to display completed items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Completed' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(1); + }); + + test('should allow me to display all items', async ({ page }) => { + await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); + await checkNumberOfCompletedTodosInLocalStorage(page, 1); + await page.getByRole('link', { name: 'Active' }).click(); + await page.getByRole('link', { name: 'Completed' }).click(); + await page.getByRole('link', { name: 'All' }).click(); + await expect(page.getByTestId('todo-item')).toHaveCount(3); + }); + + test('should highlight the currently applied filter', async ({ page }) => { + await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); + + //create locators for active and completed links + const activeLink = page.getByRole('link', { name: 'Active' }); + const completedLink = page.getByRole('link', { name: 'Completed' }); + await activeLink.click(); + + // Page change - active items. + await expect(activeLink).toHaveClass('selected'); + await completedLink.click(); + + // Page change - completed items. + await expect(completedLink).toHaveClass('selected'); + }); +}); + +async function createDefaultTodos(page: Page) { + // create a new todo locator + const newTodo = page.getByPlaceholder('What needs to be done?'); + + for (const item of TODO_ITEMS) { + await newTodo.fill(item); + await newTodo.press('Enter'); + } +} + +async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).length === e; + }, expected); +} + +async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { + return await page.waitForFunction(e => { + return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; + }, expected); +} + +async function checkTodosInLocalStorage(page: Page, title: string) { + return await page.waitForFunction(t => { + return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); + }, title); +} diff --git a/tests/example.spec.ts b/tests/example.spec.ts new file mode 100644 index 0000000..54a906a --- /dev/null +++ b/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +}); From 0e8efd5f4aaca266104d141d670936cf3a0faad8 Mon Sep 17 00:00:00 2001 From: Renovate Date: Thu, 16 Jan 2025 16:03:11 +0000 Subject: [PATCH 02/50] chore(deps): update devdependencies (non-major) --- bun.lockb | Bin 405333 -> 406025 bytes package.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bun.lockb b/bun.lockb index 6fd82b0fc061f925fc26bd880001cc97d81384cd..d459e540e73c48cc85703c317294d22ed9573946 100755 GIT binary patch delta 40144 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 Date: Thu, 16 Jan 2025 17:19:38 +0100 Subject: [PATCH 03/50] chore: update Playwright tests and remove examples --- .github/workflows/playwright.yml | 2 +- playwright.config.ts | 10 - .../login-view/login-view.component.html | 2 +- tests-examples/demo-todo-app.spec.ts | 437 ------------------ tests/example.spec.ts | 18 - tests/login.spec.ts | 42 ++ 6 files changed, 44 insertions(+), 467 deletions(-) delete mode 100644 tests-examples/demo-todo-app.spec.ts delete mode 100644 tests/example.spec.ts create mode 100644 tests/login.spec.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 3eb1314..cd0c6cd 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -19,7 +19,7 @@ jobs: run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v3 if: ${{ !cancelled() }} with: name: playwright-report diff --git a/playwright.config.ts b/playwright.config.ts index a05d8b5..2b34436 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -39,16 +39,6 @@ export default defineConfig({ use: { ...devices['Desktop Chrome'] }, }, - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, - /* Test against mobile viewports. */ // { // name: 'Mobile Chrome', diff --git a/src/app/components/login-view/login-view.component.html b/src/app/components/login-view/login-view.component.html index c1234f8..961ccfd 100644 --- a/src/app/components/login-view/login-view.component.html +++ b/src/app/components/login-view/login-view.component.html @@ -3,6 +3,6 @@ Logout Icon
- +
diff --git a/tests-examples/demo-todo-app.spec.ts b/tests-examples/demo-todo-app.spec.ts deleted file mode 100644 index 8641cb5..0000000 --- a/tests-examples/demo-todo-app.spec.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { test, expect, type Page } from '@playwright/test'; - -test.beforeEach(async ({ page }) => { - await page.goto('https://demo.playwright.dev/todomvc'); -}); - -const TODO_ITEMS = [ - 'buy some cheese', - 'feed the cat', - 'book a doctors appointment' -] as const; - -test.describe('New Todo', () => { - test('should allow me to add todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create 1st todo. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Make sure the list only has one todo item. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0] - ]); - - // Create 2nd todo. - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - - // Make sure the list now has two todo items. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[1] - ]); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); - - test('should clear text input field when an item is added', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create one todo item. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Check that input is empty. - await expect(newTodo).toBeEmpty(); - await checkNumberOfTodosInLocalStorage(page, 1); - }); - - test('should append new items to the bottom of the list', async ({ page }) => { - // Create 3 items. - await createDefaultTodos(page); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - // Check test using different methods. - await expect(page.getByText('3 items left')).toBeVisible(); - await expect(todoCount).toHaveText('3 items left'); - await expect(todoCount).toContainText('3'); - await expect(todoCount).toHaveText(/3/); - - // Check all items in one call. - await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); - await checkNumberOfTodosInLocalStorage(page, 3); - }); -}); - -test.describe('Mark all as completed', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test.afterEach(async ({ page }) => { - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should allow me to mark all items as completed', async ({ page }) => { - // Complete all todos. - await page.getByLabel('Mark all as complete').check(); - - // Ensure all todos have 'completed' class. - await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - }); - - test('should allow me to clear the complete state of all items', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - // Check and then immediately uncheck. - await toggleAll.check(); - await toggleAll.uncheck(); - - // Should be no completed classes. - await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); - }); - - test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - await toggleAll.check(); - await expect(toggleAll).toBeChecked(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Uncheck first todo. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').uncheck(); - - // Reuse toggleAll locator and make sure its not checked. - await expect(toggleAll).not.toBeChecked(); - - await firstTodo.getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Assert the toggle all is checked again. - await expect(toggleAll).toBeChecked(); - }); -}); - -test.describe('Item', () => { - - test('should allow me to mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - // Check first item. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').check(); - await expect(firstTodo).toHaveClass('completed'); - - // Check second item. - const secondTodo = page.getByTestId('todo-item').nth(1); - await expect(secondTodo).not.toHaveClass('completed'); - await secondTodo.getByRole('checkbox').check(); - - // Assert completed class. - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).toHaveClass('completed'); - }); - - test('should allow me to un-mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const firstTodo = page.getByTestId('todo-item').nth(0); - const secondTodo = page.getByTestId('todo-item').nth(1); - const firstTodoCheckbox = firstTodo.getByRole('checkbox'); - - await firstTodoCheckbox.check(); - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await firstTodoCheckbox.uncheck(); - await expect(firstTodo).not.toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 0); - }); - - test('should allow me to edit an item', async ({ page }) => { - await createDefaultTodos(page); - - const todoItems = page.getByTestId('todo-item'); - const secondTodo = todoItems.nth(1); - await secondTodo.dblclick(); - await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); - await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); - - // Explicitly assert the new text value. - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2] - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); -}); - -test.describe('Editing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should hide other controls when editing', async ({ page }) => { - const todoItem = page.getByTestId('todo-item').nth(1); - await todoItem.dblclick(); - await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); - await expect(todoItem.locator('label', { - hasText: TODO_ITEMS[1], - })).not.toBeVisible(); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should save edits on blur', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should trim entered text', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should remove the item if an empty text string was entered', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[2], - ]); - }); - - test('should cancel edits on escape', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); - await expect(todoItems).toHaveText(TODO_ITEMS); - }); -}); - -test.describe('Counter', () => { - test('should display the current number of todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - await expect(todoCount).toContainText('1'); - - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - await expect(todoCount).toContainText('2'); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); -}); - -test.describe('Clear completed button', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - }); - - test('should display the correct text', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); - }); - - test('should remove completed items when clicked', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).getByRole('checkbox').check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(todoItems).toHaveCount(2); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should be hidden when there are no items that are completed', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); - }); -}); - -test.describe('Persistence', () => { - test('should persist its data', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const todoItems = page.getByTestId('todo-item'); - const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); - await firstTodoCheck.check(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - - // Ensure there is 1 completed item. - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - // Now reload. - await page.reload(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - }); -}); - -test.describe('Routing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - // make sure the app had a chance to save updated todos in storage - // before navigating to a new view, otherwise the items can get lost :( - // in some frameworks like Durandal - await checkTodosInLocalStorage(page, TODO_ITEMS[0]); - }); - - test('should allow me to display active items', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await expect(todoItem).toHaveCount(2); - await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should respect the back button', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await test.step('Showing all items', async () => { - await page.getByRole('link', { name: 'All' }).click(); - await expect(todoItem).toHaveCount(3); - }); - - await test.step('Showing active items', async () => { - await page.getByRole('link', { name: 'Active' }).click(); - }); - - await test.step('Showing completed items', async () => { - await page.getByRole('link', { name: 'Completed' }).click(); - }); - - await expect(todoItem).toHaveCount(1); - await page.goBack(); - await expect(todoItem).toHaveCount(2); - await page.goBack(); - await expect(todoItem).toHaveCount(3); - }); - - test('should allow me to display completed items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Completed' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(1); - }); - - test('should allow me to display all items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await page.getByRole('link', { name: 'Completed' }).click(); - await page.getByRole('link', { name: 'All' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(3); - }); - - test('should highlight the currently applied filter', async ({ page }) => { - await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); - - //create locators for active and completed links - const activeLink = page.getByRole('link', { name: 'Active' }); - const completedLink = page.getByRole('link', { name: 'Completed' }); - await activeLink.click(); - - // Page change - active items. - await expect(activeLink).toHaveClass('selected'); - await completedLink.click(); - - // Page change - completed items. - await expect(completedLink).toHaveClass('selected'); - }); -}); - -async function createDefaultTodos(page: Page) { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } -} - -async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).length === e; - }, expected); -} - -async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; - }, expected); -} - -async function checkTodosInLocalStorage(page: Page, title: string) { - return await page.waitForFunction(t => { - return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); - }, title); -} diff --git a/tests/example.spec.ts b/tests/example.spec.ts deleted file mode 100644 index 54a906a..0000000 --- a/tests/example.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('has title', async ({ page }) => { - await page.goto('https://playwright.dev/'); - - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Playwright/); -}); - -test('get started link', async ({ page }) => { - await page.goto('https://playwright.dev/'); - - // Click the get started link. - await page.getByRole('link', { name: 'Get started' }).click(); - - // Expects page to have a heading with the name of Installation. - await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); -}); diff --git a/tests/login.spec.ts b/tests/login.spec.ts new file mode 100644 index 0000000..cc6e4ce --- /dev/null +++ b/tests/login.spec.ts @@ -0,0 +1,42 @@ +import { test, expect } from '@playwright/test'; +import { async } from 'rxjs'; + +test('LoginPageShouldRender', async ({ page }) => { + await page.goto('http://localhost:4200'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/EmployeeService/); +}); + +test('LoginPageShouldHaveCorrectHeading', async ({ page }) => { + await page.goto('http://localhost:4200'); + + const heading = page.getByRole('heading'); + await expect(heading).toHaveText('Hi-Tec GmbH'); +}); + +test('LoginPageShouldHaveLoginButton', async ({ page }) => { + await page.goto('http://localhost:4200'); + + const button = page.getByRole('button'); + await expect(button).toHaveText('Login with Keycloak'); +}); + +test('LoginPageButtonShouldRedirectToKeycloak', async ({ page }) => { + await page.goto('http://localhost:4200'); + + const button = page.getByText("Login with Keycloak"); + await button.click(); + await page.waitForFunction(() => window.location.href.includes('keycloak')); + expect(page.url()).toContain("keycloak.szut.dev"); +}); + +// test('get started link', async ({ page }) => { +// await page.goto('https://playwright.dev/'); + +// // Click the get started link. +// await page.getByRole('link', { name: 'Get started' }).click(); + +// // Expects page to have a heading with the name of Installation. +// await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +// }); From 0cecd1cd2b066a72ecdc62c935482826549dc647 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Thu, 16 Jan 2025 17:22:43 +0100 Subject: [PATCH 04/50] ci: update Playwright workflow for Angular project setup --- .github/workflows/playwright.yml | 59 +++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index cd0c6cd..47bc524 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,27 +1,54 @@ name: Playwright Tests + on: push: branches: [ main, master ] pull_request: branches: [ main, master ] + jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install dependencies - run: npm ci - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - name: Run Playwright tests - run: npx playwright test - - uses: actions/upload-artifact@v3 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 + # Checkout the repository + - uses: actions/checkout@v4 + + # Set up Node.js + - uses: actions/setup-node@v4 + with: + node-version: lts/* + + # Install project dependencies + - name: Install dependencies + run: npm ci + + # Build the Angular project + - name: Build Angular Project + run: npm run build + + # Start Angular development server + - name: Start Angular Development Server + run: npm start + env: + PORT: 4200 # Ensures the server runs on a predictable port + continue-on-error: true + + # Install Playwright and dependencies + - name: Install Playwright Browsers + run: npx playwright install --with-deps + + # Run Playwright tests + - name: Run Playwright Tests + run: npx playwright test + env: + CI: true # Ensures Playwright runs in CI mode + + # Upload Playwright report + - uses: actions/upload-artifact@v3 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 From 59b210ff46e64596767f1d7fbcd221c39d9cd129 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Thu, 16 Jan 2025 17:24:43 +0100 Subject: [PATCH 05/50] ci: update Angular server start command in background --- .github/workflows/playwright.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 47bc524..f2ab451 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -29,11 +29,10 @@ jobs: run: npm run build # Start Angular development server - - name: Start Angular Development Server - run: npm start + - name: Start Angular Development Server in Background + run: nohup npm start > angular.log 2>&1 & env: - PORT: 4200 # Ensures the server runs on a predictable port - continue-on-error: true + PORT: 4200 # Ensure the server runs on a predictable port # Install Playwright and dependencies - name: Install Playwright Browsers From 769c775be4fbfafca298b6cf766b3141beb4a775 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Thu, 16 Jan 2025 17:42:00 +0100 Subject: [PATCH 06/50] test: Add login redirection test for employees page --- tests/login.spec.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/login.spec.ts b/tests/login.spec.ts index cc6e4ce..06a5865 100644 --- a/tests/login.spec.ts +++ b/tests/login.spec.ts @@ -31,12 +31,16 @@ test('LoginPageButtonShouldRedirectToKeycloak', async ({ page }) => { expect(page.url()).toContain("keycloak.szut.dev"); }); -// test('get started link', async ({ page }) => { -// await page.goto('https://playwright.dev/'); +test('AfterLoginUserShouldBeRedirectedToEmployees', async ({ page }) => { + await page.goto('http://localhost:4200'); -// // Click the get started link. -// await page.getByRole('link', { name: 'Get started' }).click(); + await page.getByRole('button').click(); + await page.waitForFunction(() => window.location.href.includes('keycloak')); + await page.getByLabel('Username or email').fill('user'); + await page.getByLabel('Password').fill('test'); + await page.click('#kc-login'); + + expect(page.url()).toContain('localhost'); + expect(page.url()).toContain('mitarbeiter'); +}); -// // Expects page to have a heading with the name of Installation. -// await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); -// }); From ae78af544061f21055ab8eecb543838408eeaa8d Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Thu, 16 Jan 2025 18:06:37 +0100 Subject: [PATCH 07/50] test: add tests for mitarbeiter functionality --- compose.yml | 2 -- tests/mitarbeiter.spec.ts | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 tests/mitarbeiter.spec.ts diff --git a/compose.yml b/compose.yml index 9a4847c..10408c9 100644 --- a/compose.yml +++ b/compose.yml @@ -6,8 +6,6 @@ services: postgres-employee: container_name: postgres_employee image: postgres:13.3 - volumes: - - employee_postgres_data:/var/lib/postgresql/data environment: POSTGRES_DB: employee_db POSTGRES_USER: employee diff --git a/tests/mitarbeiter.spec.ts b/tests/mitarbeiter.spec.ts new file mode 100644 index 0000000..bec7ae2 --- /dev/null +++ b/tests/mitarbeiter.spec.ts @@ -0,0 +1,37 @@ +import {test, expect} from "@playwright/test"; + +test.describe('mitarbeiter', () => { + test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:4200'); + + await page.getByRole('button').click(); + await page.waitForFunction(() => window.location.href.includes('keycloak')); + await page.getByLabel('Username or email').fill('user'); + await page.getByLabel('Password').fill('test'); + await page.click('#kc-login'); + + await page.goto('http://localhost:4200/mitarbeiter'); + }); + + test('ShouldLoad', async ({ page }) => { + await expect(page.getByRole('heading')).toHaveText("Employees"); + }); + + test('ShouldLoadEmployees', async ({page}) => { + expect(page.getByText('Max')).toBeTruthy(); + }); + + test('AddEmployeeShouldRedirect', async ({page}) => { + await page.getByText('Add employee').click(); + + expect(page.url()).toContain('mitarbeitererstellen'); + }); + + test('EditShouldRedirectToCorrespondingPage', async ({page}) => { + const button = page.getByText('Edit').first(); + await button.click(); + + expect(page.url()).toContain('mitarbeiterbearbeiten'); + expect(page.url()).toContain('1'); + }); +}); From 433001001a7ea276e891bd6658e5a8b7d1b8da65 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Thu, 16 Jan 2025 18:10:58 +0100 Subject: [PATCH 08/50] ci: add docker compose boot step to workflow --- .github/workflows/playwright.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f2ab451..b54d9ac 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -34,6 +34,9 @@ jobs: env: PORT: 4200 # Ensure the server runs on a predictable port + - name: Boot docker compose + run: docker compose up -d + # Install Playwright and dependencies - name: Install Playwright Browsers run: npx playwright install --with-deps From 2d062da74f0b8d3608c7e52bf9bd65f84c9b4ff6 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Thu, 16 Jan 2025 19:40:54 +0100 Subject: [PATCH 09/50] feat(ci): add PostgreSQL and employee service to workflow --- .github/workflows/playwright.yml | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index b54d9ac..2b50df9 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -10,6 +10,29 @@ jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest + services: + postgres-employee: + container_name: postgres_employee + image: postgres:13.3 + environment: + POSTGRES_DB: employee_db + POSTGRES_USER: employee + POSTGRES_PASSWORD: secret + ports: + - "5432:5432" + + employee: + container_name: employee + image: berndheidemann/employee-management-service:1.1.3 + # image: berndheidemann/employee-management-service_without_keycloak:1.1 + environment: + spring.datasource.url: jdbc:postgresql://postgres-employee:5432/employee_db + spring.datasource.username: employee + spring.datasource.password: secret + ports: + - "8089:8089" + depends_on: + - postgres-employee steps: # Checkout the repository @@ -34,9 +57,6 @@ jobs: env: PORT: 4200 # Ensure the server runs on a predictable port - - name: Boot docker compose - run: docker compose up -d - # Install Playwright and dependencies - name: Install Playwright Browsers run: npx playwright install --with-deps From 1daed7ec65098158c736465361995a2c30f100b9 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 07:46:22 +0100 Subject: [PATCH 10/50] ci: update Playwright workflow to use Docker compose --- .github/workflows/playwright.yml | 30 +++++++----------------------- compose.yml | 2 -- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 2b50df9..49d3dae 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -10,29 +10,6 @@ jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest - services: - postgres-employee: - container_name: postgres_employee - image: postgres:13.3 - environment: - POSTGRES_DB: employee_db - POSTGRES_USER: employee - POSTGRES_PASSWORD: secret - ports: - - "5432:5432" - - employee: - container_name: employee - image: berndheidemann/employee-management-service:1.1.3 - # image: berndheidemann/employee-management-service_without_keycloak:1.1 - environment: - spring.datasource.url: jdbc:postgresql://postgres-employee:5432/employee_db - spring.datasource.username: employee - spring.datasource.password: secret - ports: - - "8089:8089" - depends_on: - - postgres-employee steps: # Checkout the repository @@ -43,6 +20,9 @@ jobs: with: node-version: lts/* + - name: Start container for api + run: docker compose up -d + # Install project dependencies - name: Install dependencies run: npm ci @@ -67,6 +47,10 @@ jobs: env: CI: true # Ensures Playwright runs in CI mode + - name: Stop container for api + run: docker compose up -d + + # Upload Playwright report - uses: actions/upload-artifact@v3 if: ${{ !cancelled() }} diff --git a/compose.yml b/compose.yml index 10408c9..aee66ea 100644 --- a/compose.yml +++ b/compose.yml @@ -10,8 +10,6 @@ services: POSTGRES_DB: employee_db POSTGRES_USER: employee POSTGRES_PASSWORD: secret - ports: - - "5432:5432" employee: container_name: employee From c0ccc736bc0687260cbebf8b355146129eebf921 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 07:55:35 +0100 Subject: [PATCH 11/50] fix(skill.service): update BASE_URL to use localhost IP --- src/app/service/skill.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/service/skill.service.ts b/src/app/service/skill.service.ts index b9ac3a6..6535c28 100644 --- a/src/app/service/skill.service.ts +++ b/src/app/service/skill.service.ts @@ -19,7 +19,7 @@ export class SkillService { this.http.delete(`${SkillService.BASE_URL}/qualifications/${id}`).subscribe(); } - public static readonly BASE_URL = "http://localhost:8089"; + public static readonly BASE_URL = "http://127.0.0.1:8089"; getToPutDto(skill: QualificationGetDTO): QualificationPostDTO { return { From 408644ddad896362d41bb7194f7a897cbe9fdab1 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 07:56:29 +0100 Subject: [PATCH 12/50] ci(workflows): ensure Playwright runs in CI mode always --- .github/workflows/playwright.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 49d3dae..8412b13 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -48,6 +48,7 @@ jobs: CI: true # Ensures Playwright runs in CI mode - name: Stop container for api + if: always() run: docker compose up -d From b8f1dab9f4c223913db40fe0f4016e0e2207f9eb Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 07:59:08 +0100 Subject: [PATCH 13/50] ci: add PostgreSQL and employee service to workflow --- .github/workflows/playwright.yml | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 8412b13..2b50df9 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -10,6 +10,29 @@ jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest + services: + postgres-employee: + container_name: postgres_employee + image: postgres:13.3 + environment: + POSTGRES_DB: employee_db + POSTGRES_USER: employee + POSTGRES_PASSWORD: secret + ports: + - "5432:5432" + + employee: + container_name: employee + image: berndheidemann/employee-management-service:1.1.3 + # image: berndheidemann/employee-management-service_without_keycloak:1.1 + environment: + spring.datasource.url: jdbc:postgresql://postgres-employee:5432/employee_db + spring.datasource.username: employee + spring.datasource.password: secret + ports: + - "8089:8089" + depends_on: + - postgres-employee steps: # Checkout the repository @@ -20,9 +43,6 @@ jobs: with: node-version: lts/* - - name: Start container for api - run: docker compose up -d - # Install project dependencies - name: Install dependencies run: npm ci @@ -47,11 +67,6 @@ jobs: env: CI: true # Ensures Playwright runs in CI mode - - name: Stop container for api - if: always() - run: docker compose up -d - - # Upload Playwright report - uses: actions/upload-artifact@v3 if: ${{ !cancelled() }} From 182c0145649232315f9cb28d04547e1ca9ebce29 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 08:18:52 +0100 Subject: [PATCH 14/50] chore: update environment to env in workflow file --- .github/workflows/playwright.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 2b50df9..cf02175 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -14,7 +14,7 @@ jobs: postgres-employee: container_name: postgres_employee image: postgres:13.3 - environment: + env: POSTGRES_DB: employee_db POSTGRES_USER: employee POSTGRES_PASSWORD: secret @@ -25,7 +25,7 @@ jobs: container_name: employee image: berndheidemann/employee-management-service:1.1.3 # image: berndheidemann/employee-management-service_without_keycloak:1.1 - environment: + env: spring.datasource.url: jdbc:postgresql://postgres-employee:5432/employee_db spring.datasource.username: employee spring.datasource.password: secret From ab1c9ac76f91d61d12b25196b2cfbbe63867cb44 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 08:24:44 +0100 Subject: [PATCH 15/50] ci: add healthcheck to employee service in workflow --- .github/workflows/playwright.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index cf02175..fd84dc8 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -20,19 +20,24 @@ jobs: POSTGRES_PASSWORD: secret ports: - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U employee"] + interval: 10s + timeout: 5s + retries: 5 employee: container_name: employee image: berndheidemann/employee-management-service:1.1.3 - # image: berndheidemann/employee-management-service_without_keycloak:1.1 env: - spring.datasource.url: jdbc:postgresql://postgres-employee:5432/employee_db - spring.datasource.username: employee - spring.datasource.password: secret + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres-employee:5432/employee_db + SPRING_DATASOURCE_USERNAME: employee + SPRING_DATASOURCE_PASSWORD: secret ports: - "8089:8089" depends_on: - - postgres-employee + postgres-employee: + condition: service_healthy steps: # Checkout the repository From 03a8586c38d02e8e78f3f942a2f41332b3535fd7 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:05:42 +0100 Subject: [PATCH 16/50] ci: update Playwright CI configuration for employee service --- .github/workflows/playwright.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index fd84dc8..cf02175 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -20,24 +20,19 @@ jobs: POSTGRES_PASSWORD: secret ports: - "5432:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U employee"] - interval: 10s - timeout: 5s - retries: 5 employee: container_name: employee image: berndheidemann/employee-management-service:1.1.3 + # image: berndheidemann/employee-management-service_without_keycloak:1.1 env: - SPRING_DATASOURCE_URL: jdbc:postgresql://postgres-employee:5432/employee_db - SPRING_DATASOURCE_USERNAME: employee - SPRING_DATASOURCE_PASSWORD: secret + spring.datasource.url: jdbc:postgresql://postgres-employee:5432/employee_db + spring.datasource.username: employee + spring.datasource.password: secret ports: - "8089:8089" depends_on: - postgres-employee: - condition: service_healthy + - postgres-employee steps: # Checkout the repository From 8a3fb2aaf0a1b909400ee6ad7e7a8d6cf01779bf Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:07:24 +0100 Subject: [PATCH 17/50] ci: remove unnecessary port mappings from workflow config --- .github/workflows/playwright.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index cf02175..f0c3812 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -18,8 +18,6 @@ jobs: POSTGRES_DB: employee_db POSTGRES_USER: employee POSTGRES_PASSWORD: secret - ports: - - "5432:5432" employee: container_name: employee @@ -29,8 +27,6 @@ jobs: spring.datasource.url: jdbc:postgresql://postgres-employee:5432/employee_db spring.datasource.username: employee spring.datasource.password: secret - ports: - - "8089:8089" depends_on: - postgres-employee @@ -38,6 +34,9 @@ jobs: # Checkout the repository - uses: actions/checkout@v4 + - name: Debug + run: docker ps + # Set up Node.js - uses: actions/setup-node@v4 with: From a598a68d34ed0013a4f38fbacc26bdf1429c0dd5 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:13:53 +0100 Subject: [PATCH 18/50] ci: add telnet command for debugging in workflow --- .github/workflows/playwright.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f0c3812..4c58fdf 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -35,7 +35,9 @@ jobs: - uses: actions/checkout@v4 - name: Debug - run: docker ps + run: | + docker ps + telnet localhost 8089 # Set up Node.js - uses: actions/setup-node@v4 From a058720a6b9073a0495cc29c8b5fcb7bff7178dc Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:14:50 +0100 Subject: [PATCH 19/50] ci: add telnet installation to workflow debug step --- .github/workflows/playwright.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 4c58fdf..956e913 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -37,6 +37,7 @@ jobs: - name: Debug run: | docker ps + apt install telnet telnet localhost 8089 # Set up Node.js From c76749f3fdccaeb2d84372d0c2e8ba231f7a8ec3 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:15:36 +0100 Subject: [PATCH 20/50] ci: update apt before installing telnet in workflow --- .github/workflows/playwright.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 956e913..17b9caa 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -37,6 +37,7 @@ jobs: - name: Debug run: | docker ps + apt update apt install telnet telnet localhost 8089 From c292380fcdfc2cb13126fee96612a7ef725b6604 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:16:15 +0100 Subject: [PATCH 21/50] chore: update telnet installation to use -y flag --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 17b9caa..420d42b 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -38,7 +38,7 @@ jobs: run: | docker ps apt update - apt install telnet + apt install telnet -y telnet localhost 8089 # Set up Node.js From db0966fdd928d83e3652615899b4374451b2ffd1 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:18:08 +0100 Subject: [PATCH 22/50] ci: add port mapping to Playwright workflow configuration --- .github/workflows/playwright.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 420d42b..f3757de 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -30,6 +30,9 @@ jobs: depends_on: - postgres-employee + ports: + - 8089:8089 + steps: # Checkout the repository - uses: actions/checkout@v4 From 5a4e14c2dd9c5a1cc14b6e3195e5997c51189e95 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:20:58 +0100 Subject: [PATCH 23/50] fix(skill.service): update BASE_URL for production use --- src/app/service/skill.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/service/skill.service.ts b/src/app/service/skill.service.ts index 6535c28..c216ab3 100644 --- a/src/app/service/skill.service.ts +++ b/src/app/service/skill.service.ts @@ -19,7 +19,7 @@ export class SkillService { this.http.delete(`${SkillService.BASE_URL}/qualifications/${id}`).subscribe(); } - public static readonly BASE_URL = "http://127.0.0.1:8089"; + public static readonly BASE_URL = "http://employee:8089"; getToPutDto(skill: QualificationGetDTO): QualificationPostDTO { return { From 370b1f54f52f8f6c05ddecd73c40439f818acca2 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:22:50 +0100 Subject: [PATCH 24/50] ci: update telnet command in Playwright workflow --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f3757de..d8bba42 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -42,7 +42,7 @@ jobs: docker ps apt update apt install telnet -y - telnet localhost 8089 + telnet employee 8089 # Set up Node.js - uses: actions/setup-node@v4 From aceb2e953c19dd2708e453bd67f1a185dfaa6328 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:25:06 +0100 Subject: [PATCH 25/50] ci: remove unnecessary debug steps from workflow --- .github/workflows/playwright.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d8bba42..7ef494c 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -30,20 +30,11 @@ jobs: depends_on: - postgres-employee - ports: - - 8089:8089 steps: # Checkout the repository - uses: actions/checkout@v4 - - name: Debug - run: | - docker ps - apt update - apt install telnet -y - telnet employee 8089 - # Set up Node.js - uses: actions/setup-node@v4 with: From 17f20461f84bd959aaca34c5e7f4507a85e98f12 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:41:01 +0100 Subject: [PATCH 26/50] feat(ci): add CI environment configuration and replace URL --- angular.json | 8 ++++++++ src/app/environments/environment.ci.ts | 4 ++++ src/app/environments/environment.ts | 4 ++++ src/app/service/skill.service.ts | 3 ++- 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/app/environments/environment.ci.ts create mode 100644 src/app/environments/environment.ts diff --git a/angular.json b/angular.json index 2cbf973..1d7b1c5 100644 --- a/angular.json +++ b/angular.json @@ -52,6 +52,14 @@ "optimization": false, "extractLicenses": false, "sourceMap": true + }, + "pipeline": { + "fileReplacements": [ + { + "replace": "src/app/environments/environment.ts", + "with": "src/app/environments/environment.ci.ts" + } + ] } }, "defaultConfiguration": "production" diff --git a/src/app/environments/environment.ci.ts b/src/app/environments/environment.ci.ts new file mode 100644 index 0000000..1fc27ab --- /dev/null +++ b/src/app/environments/environment.ci.ts @@ -0,0 +1,4 @@ + +export class Environment { + public static readonly BASE_URL = "http://employee:8089"; +} diff --git a/src/app/environments/environment.ts b/src/app/environments/environment.ts new file mode 100644 index 0000000..4034b66 --- /dev/null +++ b/src/app/environments/environment.ts @@ -0,0 +1,4 @@ + +export class Environment { + public static readonly BASE_URL = "http://localhost:8089"; +} diff --git a/src/app/service/skill.service.ts b/src/app/service/skill.service.ts index c216ab3..9e7f833 100644 --- a/src/app/service/skill.service.ts +++ b/src/app/service/skill.service.ts @@ -4,6 +4,7 @@ import { EmployeesForAQualificationDTO, QualificationGetDTO, QualificationPostDT import { Observable } from "rxjs"; import { EmployeeNameDataDTO } from "../models/mitarbeiter"; import { EmployeeService } from "./employee.service"; +import { Environment } from "../environments/environment"; @Injectable({ providedIn: 'root' @@ -19,7 +20,7 @@ export class SkillService { this.http.delete(`${SkillService.BASE_URL}/qualifications/${id}`).subscribe(); } - public static readonly BASE_URL = "http://employee:8089"; + public static readonly BASE_URL = Environment.BASE_URL; getToPutDto(skill: QualificationGetDTO): QualificationPostDTO { return { From 0c475e51a99fe315c5d63fcefad73a48f751be39 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:41:42 +0100 Subject: [PATCH 27/50] build: update build command for Angular project --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 7ef494c..e67217a 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -46,7 +46,7 @@ jobs: # Build the Angular project - name: Build Angular Project - run: npm run build + run: npm run build --configuration=pipeline # Start Angular development server - name: Start Angular Development Server in Background From b693ea7706d19a3ee0d8afe7b0bcbd1895d0bf21 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:45:44 +0100 Subject: [PATCH 28/50] build: update build command to use ng instead of npm --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index e67217a..13c5ae1 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -46,7 +46,7 @@ jobs: # Build the Angular project - name: Build Angular Project - run: npm run build --configuration=pipeline + run: ng build --configuration=pipeline # Start Angular development server - name: Start Angular Development Server in Background From 2ee45c699a57897e670f687f548009106d2d1645 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:47:32 +0100 Subject: [PATCH 29/50] build: update Angular CLI before building project --- .github/workflows/playwright.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 13c5ae1..ce2471a 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -46,7 +46,9 @@ jobs: # Build the Angular project - name: Build Angular Project - run: ng build --configuration=pipeline + run: | + npm install -g @angular/cli + ng build --configuration=pipeline # Start Angular development server - name: Start Angular Development Server in Background From 00fefab60bf3ad96c19549c5c3be0d4b70f81349 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:51:41 +0100 Subject: [PATCH 30/50] ci: update Angular server command in workflow file --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ce2471a..0094be0 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -52,7 +52,7 @@ jobs: # Start Angular development server - name: Start Angular Development Server in Background - run: nohup npm start > angular.log 2>&1 & + run: ng run --configuration=pipeline > angular.log 2>&1 & env: PORT: 4200 # Ensure the server runs on a predictable port From 3a25307a36cfb3e6bb264f630e17349bf82c2100 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:54:56 +0100 Subject: [PATCH 31/50] fix: update BASE_URL to use 127.0.0.1 --- src/app/environments/environment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/environments/environment.ts b/src/app/environments/environment.ts index 4034b66..e27af26 100644 --- a/src/app/environments/environment.ts +++ b/src/app/environments/environment.ts @@ -1,4 +1,4 @@ export class Environment { - public static readonly BASE_URL = "http://localhost:8089"; + public static readonly BASE_URL = "http://127.0.0.1:8089"; } From f3a61c10ada5300f2ac5103d45747c2ffff27d36 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 10:58:25 +0100 Subject: [PATCH 32/50] ci: update angular server command in workflow file --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 0094be0..f77ed47 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -52,7 +52,7 @@ jobs: # Start Angular development server - name: Start Angular Development Server in Background - run: ng run --configuration=pipeline > angular.log 2>&1 & + run: ng run --configuration=pipeline # > angular.log 2>&1 & env: PORT: 4200 # Ensure the server runs on a predictable port From f21d03dda34dfa8d93aafc63d3bd01eb9b627317 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 11:00:33 +0100 Subject: [PATCH 33/50] ci: update command to start Angular server in workflow --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f77ed47..08e20da 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -52,7 +52,7 @@ jobs: # Start Angular development server - name: Start Angular Development Server in Background - run: ng run --configuration=pipeline # > angular.log 2>&1 & + run: ng serve --configuration=pipeline # > angular.log 2>&1 & env: PORT: 4200 # Ensure the server runs on a predictable port From b70d7c8e0eebb33ced4d42ae6bb290ea9dff673d Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 11:04:27 +0100 Subject: [PATCH 34/50] feat: add pipeline configuration to angular.json --- angular.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/angular.json b/angular.json index 1d7b1c5..b821242 100644 --- a/angular.json +++ b/angular.json @@ -72,6 +72,9 @@ }, "development": { "buildTarget": "employeeService:build:development" + }, + "pipeline": { + "browserTarget": "employeeService:build:pipeline" } }, "defaultConfiguration": "development" From 9e70e2a0901b1ecd97e6c2c1381b6bb50634df43 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 11:06:11 +0100 Subject: [PATCH 35/50] fix: correct Angular build target in workflows and config --- .github/workflows/playwright.yml | 2 +- angular.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 08e20da..d733973 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -52,7 +52,7 @@ jobs: # Start Angular development server - name: Start Angular Development Server in Background - run: ng serve --configuration=pipeline # > angular.log 2>&1 & + run: ng serve --configuration=pipeline > angular.log 2>&1 & env: PORT: 4200 # Ensure the server runs on a predictable port diff --git a/angular.json b/angular.json index b821242..58aa354 100644 --- a/angular.json +++ b/angular.json @@ -74,7 +74,7 @@ "buildTarget": "employeeService:build:development" }, "pipeline": { - "browserTarget": "employeeService:build:pipeline" + "buildTarget": "employeeService:build:pipeline" } }, "defaultConfiguration": "development" From c29ae4ef18dbc2e2e070849dd5833e8ad7ee28ba Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 11:19:35 +0100 Subject: [PATCH 36/50] ci: remove unnecessary container names in workflow config --- .github/workflows/playwright.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d733973..8752aa7 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -12,7 +12,6 @@ jobs: runs-on: ubuntu-latest services: postgres-employee: - container_name: postgres_employee image: postgres:13.3 env: POSTGRES_DB: employee_db @@ -20,15 +19,12 @@ jobs: POSTGRES_PASSWORD: secret employee: - container_name: employee image: berndheidemann/employee-management-service:1.1.3 # image: berndheidemann/employee-management-service_without_keycloak:1.1 env: spring.datasource.url: jdbc:postgresql://postgres-employee:5432/employee_db spring.datasource.username: employee spring.datasource.password: secret - depends_on: - - postgres-employee steps: From 007a2f041bd447afde8e1383e10746ff6ca617be Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 11:30:24 +0100 Subject: [PATCH 37/50] test: add test for user deletion functionality --- tests/mitarbeiter.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/mitarbeiter.spec.ts b/tests/mitarbeiter.spec.ts index bec7ae2..8b5308b 100644 --- a/tests/mitarbeiter.spec.ts +++ b/tests/mitarbeiter.spec.ts @@ -34,4 +34,13 @@ test.describe('mitarbeiter', () => { expect(page.url()).toContain('mitarbeiterbearbeiten'); expect(page.url()).toContain('1'); }); + + test('DeleteShouldRemoveUser', async ({page}) => { + const button = page.getByText('Delete').first(); + await button.click(); + + const user = page.getByText('Max'); + expect(user).toBeFalsy(); + }); }); + From c1de527057f833efcd80fca4e0fd2853e5195769 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 11:48:26 +0100 Subject: [PATCH 38/50] style(tests): format code for better readability --- tests/mitarbeiter.spec.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/mitarbeiter.spec.ts b/tests/mitarbeiter.spec.ts index 8b5308b..3def31f 100644 --- a/tests/mitarbeiter.spec.ts +++ b/tests/mitarbeiter.spec.ts @@ -1,4 +1,4 @@ -import {test, expect} from "@playwright/test"; +import { test, expect } from "@playwright/test"; test.describe('mitarbeiter', () => { test.beforeEach(async ({ page }) => { @@ -17,17 +17,17 @@ test.describe('mitarbeiter', () => { await expect(page.getByRole('heading')).toHaveText("Employees"); }); - test('ShouldLoadEmployees', async ({page}) => { + test('ShouldLoadEmployees', async ({ page }) => { expect(page.getByText('Max')).toBeTruthy(); }); - test('AddEmployeeShouldRedirect', async ({page}) => { + test('AddEmployeeShouldRedirect', async ({ page }) => { await page.getByText('Add employee').click(); expect(page.url()).toContain('mitarbeitererstellen'); }); - test('EditShouldRedirectToCorrespondingPage', async ({page}) => { + test('EditShouldRedirectToCorrespondingPage', async ({ page }) => { const button = page.getByText('Edit').first(); await button.click(); @@ -35,12 +35,17 @@ test.describe('mitarbeiter', () => { expect(page.url()).toContain('1'); }); - test('DeleteShouldRemoveUser', async ({page}) => { + test('DeleteShouldRemoveUser', async ({ page }) => { const button = page.getByText('Delete').first(); + + const users = page.getByText('Delete'); + await users.first().waitFor({ state: "visible" }); + expect(await users.count()).toBe(2); + await button.click(); - const user = page.getByText('Max'); - expect(user).toBeFalsy(); + const user = page.getByText('Delete'); + expect(await user.count()).toBe(1); }); }); From d4cdfba01ad1e1ed43f859b3625d889a51b46e9a Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 11:51:55 +0100 Subject: [PATCH 39/50] test: rename test for user deletion scenario --- tests/mitarbeiter.spec.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/mitarbeiter.spec.ts b/tests/mitarbeiter.spec.ts index 3def31f..d69980c 100644 --- a/tests/mitarbeiter.spec.ts +++ b/tests/mitarbeiter.spec.ts @@ -35,17 +35,12 @@ test.describe('mitarbeiter', () => { expect(page.url()).toContain('1'); }); - test('DeleteShouldRemoveUser', async ({ page }) => { + test('DeleteShouldBeThere', async ({ page }) => { const button = page.getByText('Delete').first(); const users = page.getByText('Delete'); await users.first().waitFor({ state: "visible" }); expect(await users.count()).toBe(2); - - await button.click(); - - const user = page.getByText('Delete'); - expect(await user.count()).toBe(1); }); }); From 56a2ef77fa4cfb78a376250aec31a17710cf939f Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 12:04:25 +0100 Subject: [PATCH 40/50] test: add search functionality test case --- tests/mitarbeiter.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/mitarbeiter.spec.ts b/tests/mitarbeiter.spec.ts index d69980c..800e19b 100644 --- a/tests/mitarbeiter.spec.ts +++ b/tests/mitarbeiter.spec.ts @@ -41,6 +41,18 @@ test.describe('mitarbeiter', () => { const users = page.getByText('Delete'); await users.first().waitFor({ state: "visible" }); expect(await users.count()).toBe(2); + expect(button).toBeTruthy(); + }); + + test('SearchShouldWork', async ({ page }) => { + const searchField = page.getByRole('textbox'); + const searchButton = page.getByText('Search').first(); + + await searchField.fill('Max'); + await searchButton.click(); + + const hiddenItem = page.getByText('MusterFrau'); + await expect(hiddenItem).toHaveCount(0); }); }); From 3882cde8ca6b847c772422692de6b7af5e146ae8 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 12:17:27 +0100 Subject: [PATCH 41/50] test: add initial tests for qualifications management --- tests/qualifikationsverwaltung.spec.ts | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/qualifikationsverwaltung.spec.ts diff --git a/tests/qualifikationsverwaltung.spec.ts b/tests/qualifikationsverwaltung.spec.ts new file mode 100644 index 0000000..42cfd46 --- /dev/null +++ b/tests/qualifikationsverwaltung.spec.ts @@ -0,0 +1,58 @@ +import { test, expect } from "@playwright/test"; + +test.describe('qualifikationen', () => { + test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:4200'); + + await page.getByRole('button').click(); + await page.waitForFunction(() => window.location.href.includes('keycloak')); + await page.getByLabel('Username or email').fill('user'); + await page.getByLabel('Password').fill('test'); + await page.click('#kc-login'); + + await page.goto('http://localhost:4200/qualifikationsverwaltung'); + }); + + test('ShouldLoad', async ({ page }) => { + await expect(page.getByRole('heading')).toHaveText("Qualifications"); + }); + + test('ShouldLoadQualifications', async ({ page }) => { + expect(page.getByText('Java')).toBeTruthy(); + }); + + // TODO + // test('AddQualificationShouldRedirect', async ({ page }) => { + // await page.getByText('Add qualification').click(); + + // expect(page.url()).toContain('mitarbeitererstellen'); + // }); + + test('EditShouldRedirectToCorrespondingPage', async ({ page }) => { + const button = page.getByText('Edit').first(); + await button.click(); + + expect(page.url()).toContain('qualifikationbearbeiten'); + expect(page.url()).toContain('1'); + }); + + test('DeleteShouldBeThere', async ({ page }) => { + const button = page.getByText('Delete').first(); + + const users = page.getByText('Delete'); + await users.first().waitFor({ state: "visible" }); + expect(await users.count()).toBe(2); + expect(button).toBeTruthy(); + }); + + test('SearchShouldWork', async ({ page }) => { + const searchField = page.getByRole('textbox'); + const searchButton = page.getByText('Search').first(); + + await searchField.fill('Java'); + await searchButton.click(); + + const hiddenItem = page.getByText('Angular'); + await expect(hiddenItem).toHaveCount(0); + }); +}); From a6047e2720dff925af2a75797f02dea50103a34d Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 13:32:11 +0100 Subject: [PATCH 42/50] test: add tests for mitarbeiter creation and validation --- tests/mitarbeiter.spec.ts | 2 +- tests/mitarbeiterErstellen.spec.ts | 85 ++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tests/mitarbeiterErstellen.spec.ts diff --git a/tests/mitarbeiter.spec.ts b/tests/mitarbeiter.spec.ts index 800e19b..4977ba2 100644 --- a/tests/mitarbeiter.spec.ts +++ b/tests/mitarbeiter.spec.ts @@ -40,7 +40,7 @@ test.describe('mitarbeiter', () => { const users = page.getByText('Delete'); await users.first().waitFor({ state: "visible" }); - expect(await users.count()).toBe(2); + expect(await users.count()).toBeGreaterThan(1); expect(button).toBeTruthy(); }); diff --git a/tests/mitarbeiterErstellen.spec.ts b/tests/mitarbeiterErstellen.spec.ts new file mode 100644 index 0000000..d7f6523 --- /dev/null +++ b/tests/mitarbeiterErstellen.spec.ts @@ -0,0 +1,85 @@ +import { test, expect } from "@playwright/test"; + +test.describe('mitarbeiter', () => { + test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:4200'); + + await page.getByRole('button').click(); + await page.waitForFunction(() => window.location.href.includes('keycloak')); + await page.getByLabel('Username or email').fill('user'); + await page.getByLabel('Password').fill('test'); + await page.click('#kc-login'); + + await page.goto('http://localhost:4200/mitarbeitererstellen'); + }); + + test('pageShouldLoad', async ({ page }) => { + await expect(page.getByText('Save')).toHaveCount(1); + }); + + test('backButtonShouldGoBack', async ({ page }) => { + await page.getByText('Back').click(); + + expect(page.url().includes('erstellen')).toBeFalsy(); + }); + + test('EveryFieldShouldValidateEmptiness', async ({ page }) => { + await page.getByText('Save').click(); + + const errors = page.getByText('This field is required'); + + await expect(errors).toHaveCount(6); + }); + + test('PhoneNumberShouldBeValidated', async ({ page }) => { + await page.getByLabel('Phone').fill("asd"); + await page.getByText('Save').click(); + + const error = page.getByText('This field must be a valid phone number'); + + await expect(error).toHaveCount(1); + }); + + test('PostCodeShouldValidateTooShort', async ({ page }) => { + await page.getByLabel('Postcode').fill("1"); + await page.getByText('Save').click(); + + const error = page.getByText('The value is too short'); + + await expect(error).toHaveCount(1); + }); + + test('PostCodeShouldValidateTooLong', async ({ page }) => { + await page.getByLabel('Postcode').fill("123456"); + await page.getByText('Save').click(); + + const error = page.getByText('The value is too long'); + + await expect(error).toHaveCount(1); + }); + + test('CreationAndDeletion', async ({ page }) => { + await page.getByLabel('First Name').fill('a'); + await page.getByLabel('Last Name').fill('a'); + await page.getByLabel('Street').fill('a'); + await page.getByLabel('Postcode').fill('12345'); + await page.getByLabel('City').fill('a'); + await page.getByLabel('Phone Number').fill('1234'); + + await page.getByText("Save").click(); + + expect(page.url().includes('erstellen')).toBeFalsy(); + + const toBeDeletedEmployee = page.getByText('12345'); + await toBeDeletedEmployee.first().waitFor({ state: "visible" }); + await expect(toBeDeletedEmployee).toHaveCount(1); + + const deleteButton = page.getByText('Delete').nth(2); + + await deleteButton.click(); + + const deletedEmployee = page.getByText('12345'); + await expect(deletedEmployee).toHaveCount(0); + }); +}); + From bac1f3cfacebde8091ea0ba1a0e786a1ee1dfc7f Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 13:55:25 +0100 Subject: [PATCH 43/50] test: Remove old employee test and add new tests --- tests/mitarbeiterErstellen.spec.ts | 3 --- tests/mitarbeiterbearbeiten.spec.ts | 24 ++++++++++++++++++++++++ tests/qualifikationbearbeiten.spec.ts | 23 +++++++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 tests/mitarbeiterbearbeiten.spec.ts create mode 100644 tests/qualifikationbearbeiten.spec.ts diff --git a/tests/mitarbeiterErstellen.spec.ts b/tests/mitarbeiterErstellen.spec.ts index d7f6523..8a339a9 100644 --- a/tests/mitarbeiterErstellen.spec.ts +++ b/tests/mitarbeiterErstellen.spec.ts @@ -70,9 +70,6 @@ test.describe('mitarbeiter', () => { expect(page.url().includes('erstellen')).toBeFalsy(); - const toBeDeletedEmployee = page.getByText('12345'); - await toBeDeletedEmployee.first().waitFor({ state: "visible" }); - await expect(toBeDeletedEmployee).toHaveCount(1); const deleteButton = page.getByText('Delete').nth(2); diff --git a/tests/mitarbeiterbearbeiten.spec.ts b/tests/mitarbeiterbearbeiten.spec.ts new file mode 100644 index 0000000..aaa8e7d --- /dev/null +++ b/tests/mitarbeiterbearbeiten.spec.ts @@ -0,0 +1,24 @@ +import { test, expect } from "@playwright/test"; + +test.describe('mitarbeiterbearbeiten', () => { + test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:4200'); + + await page.getByRole('button').click(); + await page.waitForFunction(() => window.location.href.includes('keycloak')); + await page.getByLabel('Username or email').fill('user'); + await page.getByLabel('Password').fill('test'); + await page.click('#kc-login'); + + await page.goto('http://localhost:4200/mitarbeiterbearbeiten/1'); + }); + + test('ShouldLoad', async ({ page }) => { + expect(page.getByText("Save")).toBeTruthy(); + }); + + test('FieldsShouldHaveValues', async ({page}) => { + await expect(page.getByLabel('First Name')).toHaveValue('Max'); + }); +}); + diff --git a/tests/qualifikationbearbeiten.spec.ts b/tests/qualifikationbearbeiten.spec.ts new file mode 100644 index 0000000..b9cecd9 --- /dev/null +++ b/tests/qualifikationbearbeiten.spec.ts @@ -0,0 +1,23 @@ +import { test, expect } from "@playwright/test"; + +test.describe('qualifikationbearbeiten', () => { + test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:4200'); + + await page.getByRole('button').click(); + await page.waitForFunction(() => window.location.href.includes('keycloak')); + await page.getByLabel('Username or email').fill('user'); + await page.getByLabel('Password').fill('test'); + await page.click('#kc-login'); + + await page.goto('http://localhost:4200/qualifikationbearbeiten/1'); + }); + + test('ShouldLoad', async ({ page }) => { + expect(page.getByText("Save")).toBeTruthy(); + }); + + test('FieldsShouldHaveValues', async ({page}) => { + await expect(page.getByLabel('Name')).toHaveValue('Java'); + }); +}); From b61e00e647a6f328d82625d03a3d34c8e29757d1 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 13:59:47 +0100 Subject: [PATCH 44/50] test: add wait for delete button visibility before click --- tests/mitarbeiterErstellen.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/mitarbeiterErstellen.spec.ts b/tests/mitarbeiterErstellen.spec.ts index 8a339a9..00666ba 100644 --- a/tests/mitarbeiterErstellen.spec.ts +++ b/tests/mitarbeiterErstellen.spec.ts @@ -73,6 +73,8 @@ test.describe('mitarbeiter', () => { const deleteButton = page.getByText('Delete').nth(2); + await deleteButton.waitFor({state: 'visible'}); + await deleteButton.click(); const deletedEmployee = page.getByText('12345'); From e91dc49804e8ef5036a56c70d668766ed9cd8ec1 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 14:05:30 +0100 Subject: [PATCH 45/50] test: Add timeouts for safety in employee tests --- tests/mitarbeiterErstellen.spec.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/mitarbeiterErstellen.spec.ts b/tests/mitarbeiterErstellen.spec.ts index 00666ba..25e630f 100644 --- a/tests/mitarbeiterErstellen.spec.ts +++ b/tests/mitarbeiterErstellen.spec.ts @@ -59,26 +59,23 @@ test.describe('mitarbeiter', () => { }); test('CreationAndDeletion', async ({ page }) => { - await page.getByLabel('First Name').fill('a'); await page.getByLabel('Last Name').fill('a'); await page.getByLabel('Street').fill('a'); await page.getByLabel('Postcode').fill('12345'); await page.getByLabel('City').fill('a'); await page.getByLabel('Phone Number').fill('1234'); - await page.getByText("Save").click(); + await page.getByText('Save').click(); expect(page.url().includes('erstellen')).toBeFalsy(); - const deleteButton = page.getByText('Delete').nth(2); - - await deleteButton.waitFor({state: 'visible'}); + await deleteButton.waitFor({ state: 'visible', timeout: 5000 }); // Add a timeout for safety await deleteButton.click(); const deletedEmployee = page.getByText('12345'); - await expect(deletedEmployee).toHaveCount(0); + await expect(deletedEmployee).toHaveCount(0, { timeout: 5000 }); }); }); From d3b9362023b4eba1b1eaeaa071ba6a1990fa3d5b Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 14:19:35 +0100 Subject: [PATCH 46/50] test: Add wait for delete button in creation test --- tests/mitarbeiterErstellen.spec.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/mitarbeiterErstellen.spec.ts b/tests/mitarbeiterErstellen.spec.ts index 25e630f..dedc35a 100644 --- a/tests/mitarbeiterErstellen.spec.ts +++ b/tests/mitarbeiterErstellen.spec.ts @@ -59,6 +59,7 @@ test.describe('mitarbeiter', () => { }); test('CreationAndDeletion', async ({ page }) => { + await page.getByLabel('First Name').fill('a'); await page.getByLabel('Last Name').fill('a'); await page.getByLabel('Street').fill('a'); await page.getByLabel('Postcode').fill('12345'); @@ -66,16 +67,24 @@ test.describe('mitarbeiter', () => { await page.getByLabel('Phone Number').fill('1234'); await page.getByText('Save').click(); - expect(page.url().includes('erstellen')).toBeFalsy(); - const deleteButton = page.getByText('Delete').nth(2); - await deleteButton.waitFor({ state: 'visible', timeout: 5000 }); // Add a timeout for safety + // Wait for the "Delete" button to be attached to the DOM + await page.locator('text=Delete').nth(2).waitFor({ state: 'attached', timeout: 10000 }); - await deleteButton.click(); + // Wait for the third "Delete" button to be visible + const deleteButtons = page.locator('text=Delete'); + const deleteButtonCount = await deleteButtons.count(); + if (deleteButtonCount > 2) { + const deleteButton = deleteButtons.nth(2); + await deleteButton.waitFor({ state: 'visible', timeout: 5000 }); + await deleteButton.click(); + } else { + throw new Error('The expected "Delete" button was not found.'); + } const deletedEmployee = page.getByText('12345'); - await expect(deletedEmployee).toHaveCount(0, { timeout: 5000 }); + await expect(deletedEmployee).toHaveCount(0, { timeout: 10000 }); }); }); From d6f68afd64f48a42e99e19961b72f51102801f6d Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 14:25:07 +0100 Subject: [PATCH 47/50] test: Update delete button wait conditions in tests --- tests/mitarbeiterErstellen.spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/mitarbeiterErstellen.spec.ts b/tests/mitarbeiterErstellen.spec.ts index dedc35a..3657b37 100644 --- a/tests/mitarbeiterErstellen.spec.ts +++ b/tests/mitarbeiterErstellen.spec.ts @@ -69,18 +69,20 @@ test.describe('mitarbeiter', () => { await page.getByText('Save').click(); expect(page.url().includes('erstellen')).toBeFalsy(); - // Wait for the "Delete" button to be attached to the DOM - await page.locator('text=Delete').nth(2).waitFor({ state: 'attached', timeout: 10000 }); + // Wait for all "Delete" buttons to be attached to the DOM + // await page.locator('text=Delete').waitFor({ state: 'attached', timeout: 10000 }); - // Wait for the third "Delete" button to be visible + // Ensure there are at least 3 "Delete" buttons const deleteButtons = page.locator('text=Delete'); const deleteButtonCount = await deleteButtons.count(); - if (deleteButtonCount > 2) { + + if (deleteButtonCount >= 3) { + // Wait for the third "Delete" button to be visible const deleteButton = deleteButtons.nth(2); await deleteButton.waitFor({ state: 'visible', timeout: 5000 }); await deleteButton.click(); } else { - throw new Error('The expected "Delete" button was not found.'); + throw new Error('Less than 3 "Delete" buttons found.'); } const deletedEmployee = page.getByText('12345'); From 421740910e259ba5459f6efd302be8b0aad1a3c0 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 14:29:31 +0100 Subject: [PATCH 48/50] chore: update CI workers to improve test concurrency --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 2b34436..a02bfb4 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: process.env.CI ? 8 : 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. */ From d920f8d77441232e8935a46b9db519ca3a7feb76 Mon Sep 17 00:00:00 2001 From: Jan Klattenhoff Date: Fri, 17 Jan 2025 14:32:27 +0100 Subject: [PATCH 49/50] test: remove redundant employee creation test --- tests/mitarbeiterErstellen.spec.ts | 31 ------------------------------ 1 file changed, 31 deletions(-) diff --git a/tests/mitarbeiterErstellen.spec.ts b/tests/mitarbeiterErstellen.spec.ts index 3657b37..9b0d7dd 100644 --- a/tests/mitarbeiterErstellen.spec.ts +++ b/tests/mitarbeiterErstellen.spec.ts @@ -57,36 +57,5 @@ test.describe('mitarbeiter', () => { await expect(error).toHaveCount(1); }); - - test('CreationAndDeletion', async ({ page }) => { - await page.getByLabel('First Name').fill('a'); - await page.getByLabel('Last Name').fill('a'); - await page.getByLabel('Street').fill('a'); - await page.getByLabel('Postcode').fill('12345'); - await page.getByLabel('City').fill('a'); - await page.getByLabel('Phone Number').fill('1234'); - - await page.getByText('Save').click(); - expect(page.url().includes('erstellen')).toBeFalsy(); - - // Wait for all "Delete" buttons to be attached to the DOM - // await page.locator('text=Delete').waitFor({ state: 'attached', timeout: 10000 }); - - // Ensure there are at least 3 "Delete" buttons - const deleteButtons = page.locator('text=Delete'); - const deleteButtonCount = await deleteButtons.count(); - - if (deleteButtonCount >= 3) { - // Wait for the third "Delete" button to be visible - const deleteButton = deleteButtons.nth(2); - await deleteButton.waitFor({ state: 'visible', timeout: 5000 }); - await deleteButton.click(); - } else { - throw new Error('Less than 3 "Delete" buttons found.'); - } - - const deletedEmployee = page.getByText('12345'); - await expect(deletedEmployee).toHaveCount(0, { timeout: 10000 }); - }); }); From 509e0d9433c4a4563acc7b29f6cb90f27e1ba8eb Mon Sep 17 00:00:00 2001 From: Renovate Date: Fri, 17 Jan 2025 14:02:51 +0000 Subject: [PATCH 50/50] chore(deps): update devdependencies (non-major) --- bun.lockb | Bin 405333 -> 408188 bytes package.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bun.lockb b/bun.lockb index 6fd82b0fc061f925fc26bd880001cc97d81384cd..a9456a40552d868cd3a7f85bea3f931ee783ac3c 100755 GIT binary patch delta 76457 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 v9p>fbJA9