-
Notifications
You must be signed in to change notification settings - Fork 8
[DRAFT] Playwright tests #858
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next-minor
Are you sure you want to change the base?
Changes from 11 commits
23d053b
21fc9c4
9400d49
04fda54
915b5aa
05e775d
ed577bf
cc34bce
5c54e97
25f4384
713ee44
ba55011
4a62ef1
569e47d
782a41a
6b3a413
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,11 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILDING_BLOCK_MANAGEMENT_LIST_TEST_IDS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILDING_BLOCK_MANAGEMENT_CREATE_TEST_IDS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILDING_BLOCK_MANAGEMENT_UPLOAD_TEST_IDS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILDING_BLOCK_MANAGEMENT_DETAIL_TEST_IDS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILDING_BLOCK_MANAGEMENT_METADATA_TEST_IDS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILDING_BLOCK_MANAGEMENT_ARTWORK_TEST_IDS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILDING_BLOCK_MANAGEMENT_PLUGINS_TEST_IDS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILDING_BLOCK_MANAGEMENT_DETAIL_ACTIONS_TEST_IDS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BUILDING_BLOCK_VERSION_OPTION_TEST_ID_PREFIX, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '../../frontend/projects/valtimo/building-block-management/src/lib/constants/building-block-management.test-ids'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+13
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Add the required license header. This TypeScript file starts with Proposed header+/*
+ * Copyright 2015-2026 Ritense BV, the Netherlands.
+ *
+ * Licensed under EUPL, Version 1.2 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
export {As per path instructions, TypeScript files must start with the standard EUPL license header. 📝 Committable suggestion
Suggested change
Source: Path instructions |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| /* | ||
| * Copyright 2015-2026 Ritense BV, the Netherlands. | ||
| * | ||
| * Licensed under EUPL, Version 1.2 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" basis, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| export { | ||
| DASHBOARD_TEST_IDS, | ||
| DASHBOARD_WIDGET_TEST_ID_PREFIX, | ||
| } from '../../frontend/projects/valtimo/dashboard/src/lib/constants/dashboard.test-ids'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| export * from './building-block-management-test-ids'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Add the required license header. Line 1 starts with code. Prepend the standard EUPL license header before this export. As per path instructions, each Source: Path instructions |
||
| export * from './case-management-test-ids'; | ||
| export * from './dashboard-management-test-ids'; | ||
| export * from './dashboard-test-ids'; | ||
| export * from './components-test-ids'; | ||
| export * from './decision-test-ids'; | ||
| export * from './form-management-test-ids'; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| /* | ||
| * Copyright 2015-2026 Ritense BV, the Netherlands. | ||
| * | ||
| * Licensed under EUPL, Version 1.2 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" basis, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| import {expect, type Locator, type Page} from '@playwright/test'; | ||
|
|
||
| /** | ||
| * Wrapper for the Valtimo `v-overflow-menu` component (the "More" menus in | ||
| * admin detail headers and carbon-list rows). | ||
| * | ||
| * The menu content is rendered in an overlay outside the trigger, so options are | ||
| * looked up page-wide rather than inside the trigger element. Options are only | ||
| * present in the DOM while the menu is open. | ||
| */ | ||
| export class OverflowMenu { | ||
| constructor( | ||
| private readonly page: Page, | ||
| private readonly triggerTestId: string | ||
| ) {} | ||
|
|
||
| get trigger(): Locator { | ||
| return this.page.getByTestId(this.triggerTestId); | ||
| } | ||
|
|
||
| get menu(): Locator { | ||
| return this.page.getByRole('menu'); | ||
| } | ||
|
|
||
| option(optionTestId: string): Locator { | ||
| return this.page.getByTestId(optionTestId); | ||
| } | ||
|
|
||
| async open() { | ||
| await expect(this.trigger).toBeEnabled(); | ||
| await this.trigger.click(); | ||
| await expect(this.menu).toBeVisible(); | ||
| } | ||
|
|
||
| async close() { | ||
| await this.page.keyboard.press('Escape'); | ||
| await expect(this.menu).not.toBeVisible(); | ||
| } | ||
|
|
||
| /** Open the menu and click one of its options. */ | ||
| async selectOption(optionTestId: string) { | ||
| await this.open(); | ||
| const option = this.option(optionTestId); | ||
| await expect(option).toBeVisible(); | ||
| await option.click(); | ||
| } | ||
|
|
||
| /** Labels of the options currently offered, in render order. */ | ||
| async optionLabels(): Promise<string[]> { | ||
| const labels = await this.menu.getByRole('menuitem').allInnerTexts(); | ||
| return labels.map(label => label.trim()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,219 @@ | ||
| /* | ||
| * Copyright 2015-2026 Ritense BV, the Netherlands. | ||
| * | ||
| * Licensed under EUPL, Version 1.2 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" basis, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| import {expect, type Locator, type Page} from '@playwright/test'; | ||
| import { | ||
| CONFIRMATION_MODAL_TEST_IDS, | ||
| SCHEMA_EDITOR_REQUIRED_PROPERTY_TEST_ID_PREFIX, | ||
| SCHEMA_EDITOR_TEST_IDS, | ||
| } from '../../constants'; | ||
|
|
||
| /** | ||
| * Wrapper for `valtimo-schema-editor` (`@valtimo/components`), the JSON-schema | ||
| * editor used on the document tabs of case and building block definitions. | ||
| * | ||
| * The editor embeds `vanilla-jsoneditor`, a third-party component whose controls | ||
| * cannot carry `data-test-id` attributes. Those parts (mode switch, search, | ||
| * parse-error panel, tree nodes) are therefore addressed by their accessible | ||
| * name, title or the library's own class names; everything Valtimo owns — the | ||
| * save button, the required-fields panel and its checkboxes — uses test ids. | ||
| */ | ||
| export class SchemaEditor { | ||
| constructor(private readonly page: Page) {} | ||
|
|
||
| // ─── Root ───────────────────────────────────────────────────────── | ||
|
|
||
| get root(): Locator { | ||
| return this.page.getByTestId(SCHEMA_EDITOR_TEST_IDS.editor); | ||
| } | ||
|
|
||
| get saveButton(): Locator { | ||
| return this.page.getByTestId(SCHEMA_EDITOR_TEST_IDS.saveButton); | ||
| } | ||
|
|
||
| get manageRequiredFieldsButton(): Locator { | ||
| return this.page.getByTestId(SCHEMA_EDITOR_TEST_IDS.manageRequiredFieldsButton); | ||
| } | ||
|
|
||
| get confirmSaveButton(): Locator { | ||
| return this.page.getByTestId(CONFIRMATION_MODAL_TEST_IDS.confirmButton); | ||
| } | ||
|
|
||
| // ─── Tree mode (third-party markup) ─────────────────────────────── | ||
|
|
||
| /** Property/attribute names rendered by the tree view. */ | ||
| get keys(): Locator { | ||
| return this.root.locator('.jse-key'); | ||
| } | ||
|
|
||
| /** Values rendered by the tree view — field types and descriptions included. */ | ||
| get values(): Locator { | ||
| return this.root.locator('.jse-value'); | ||
| } | ||
|
|
||
| async keyTexts(): Promise<string[]> { | ||
| return (await this.keys.allInnerTexts()).map(text => text.trim()); | ||
| } | ||
|
|
||
| async valueTexts(): Promise<string[]> { | ||
| return (await this.values.allInnerTexts()).map(text => text.trim()); | ||
| } | ||
|
|
||
| async waitForLoaded() { | ||
| await expect(this.root).toBeVisible(); | ||
| await expect(this.keys.first()).toBeVisible(); | ||
| } | ||
|
|
||
| // ─── Mode switching ─────────────────────────────────────────────── | ||
|
|
||
| async switchToTextMode() { | ||
| await this.root.getByRole('button', {name: 'text', exact: true}).click(); | ||
| await expect(this.textContent).toBeVisible(); | ||
| } | ||
|
|
||
| async switchToTreeMode() { | ||
| await this.root.getByRole('button', {name: 'tree', exact: true}).click(); | ||
| await expect(this.keys.first()).toBeVisible(); | ||
| } | ||
|
|
||
| /** The CodeMirror surface of the editor's text mode. */ | ||
| get textContent(): Locator { | ||
| return this.root.locator('.cm-content').first(); | ||
| } | ||
|
|
||
| // ─── Editing ────────────────────────────────────────────────────── | ||
|
|
||
| /** | ||
| * Replace the whole document with `content` by typing it in text mode. | ||
| * | ||
| * Typing rather than pasting is deliberate: the editor only emits its change | ||
| * event on real input, and a clipboard paste does not reliably reach the | ||
| * CodeMirror instance in headless Chromium. | ||
| */ | ||
| async replaceContent(content: unknown) { | ||
| await this.switchToTextMode(); | ||
| await this.textContent.click(); | ||
| await this.page.keyboard.press('ControlOrMeta+a'); | ||
| await this.page.keyboard.type(typeof content === 'string' ? content : JSON.stringify(content)); | ||
| } | ||
|
|
||
| /** Save and confirm. Returns nothing — await the API response in the caller. */ | ||
| async save() { | ||
| await expect(this.saveButton).toBeEnabled(); | ||
| await this.saveButton.click(); | ||
| await expect(this.confirmSaveButton).toBeVisible(); | ||
| await this.confirmSaveButton.click(); | ||
| } | ||
|
|
||
| // ─── Parse errors ───────────────────────────────────────────────── | ||
|
|
||
| /** Panel the embedded editor shows when the text is not parseable JSON. */ | ||
| get parseError(): Locator { | ||
| return this.root.locator('.jse-message.jse-error'); | ||
| } | ||
|
|
||
| get autoRepairButton(): Locator { | ||
| return this.root.getByRole('button', {name: 'Auto repair'}); | ||
| } | ||
|
|
||
| // ─── Search ─────────────────────────────────────────────────────── | ||
|
|
||
| get searchBox(): Locator { | ||
| return this.root.locator('.jse-search-box'); | ||
| } | ||
|
|
||
| get searchInput(): Locator { | ||
| return this.searchBox.locator('input').first(); | ||
| } | ||
|
|
||
| /** Matches highlighted in the tree for the active search term. */ | ||
| get searchHighlights(): Locator { | ||
| return this.root.locator('.jse-highlight'); | ||
| } | ||
|
|
||
| async openSearch() { | ||
| await this.root.getByTitle(/^Search/).click(); | ||
| await expect(this.searchInput).toBeVisible(); | ||
| } | ||
|
|
||
| async closeSearch() { | ||
| await this.page.keyboard.press('Escape'); | ||
| await expect(this.searchBox).toHaveCount(0); | ||
| } | ||
|
|
||
| async search(term: string) { | ||
| await this.searchInput.fill(term); | ||
| } | ||
|
|
||
| /** | ||
| * The editor's match counter: `"<current>/<total>"` while there are matches and | ||
| * `"0"` when there are none. | ||
| */ | ||
| async searchResultCount(): Promise<string> { | ||
| return (await this.searchBox.innerText()).trim(); | ||
| } | ||
|
|
||
| // ─── Required fields panel ──────────────────────────────────────── | ||
|
|
||
| get requiredFieldsPanel(): Locator { | ||
| return this.page.getByTestId(SCHEMA_EDITOR_TEST_IDS.requiredFieldsPanel); | ||
| } | ||
|
|
||
| get requiredFieldsPanelCloseButton(): Locator { | ||
| return this.page.getByTestId(SCHEMA_EDITOR_TEST_IDS.requiredFieldsPanelCloseButton); | ||
| } | ||
|
|
||
| /** | ||
| * Checkbox of a required field, addressed by its path within the schema: | ||
| * `requiredFieldCheckbox('applicantName')` for a root property, | ||
| * `requiredFieldCheckbox('address', 'street')` for a nested one. | ||
| */ | ||
| requiredFieldCheckbox(...path: string[]): Locator { | ||
| return this.page.getByTestId( | ||
| `${SCHEMA_EDITOR_REQUIRED_PROPERTY_TEST_ID_PREFIX}${path.join('.')}` | ||
| ); | ||
| } | ||
|
|
||
| async openRequiredFieldsPanel() { | ||
| await this.manageRequiredFieldsButton.click(); | ||
| await expect(this.requiredFieldsPanel).toBeVisible(); | ||
| } | ||
|
|
||
| async closeRequiredFieldsPanel() { | ||
| await this.requiredFieldsPanelCloseButton.click(); | ||
| } | ||
|
|
||
| /** Carbon's `cds-checkbox` only emits `checkedChange` for a click on its label. */ | ||
| async toggleRequiredField(...path: string[]) { | ||
| await this.requiredFieldCheckbox(...path) | ||
| .locator('label') | ||
| .click(); | ||
| } | ||
|
|
||
| async isRequiredFieldChecked(...path: string[]): Promise<boolean> { | ||
| return this.requiredFieldCheckbox(...path) | ||
| .locator('input') | ||
| .isChecked(); | ||
| } | ||
|
|
||
| async assertRequiredFieldChecked(...path: string[]) { | ||
| await expect(this.requiredFieldCheckbox(...path).locator('input')).toBeChecked(); | ||
| } | ||
|
|
||
| async assertRequiredFieldNotChecked(...path: string[]) { | ||
| await expect(this.requiredFieldCheckbox(...path).locator('input')).not.toBeChecked(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Add the standard EUPL license header at the start of this file.
Line 14 starts code before the required TypeScript header can end. Add the repository standard header before all code. As per path instructions, each
.tsfile must start with the standard EUPL header.Source: Path instructions