diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json
index b6480ea03e3..3ca8efe7dac 100644
--- a/frontend/language/src/nb.json
+++ b/frontend/language/src/nb.json
@@ -763,12 +763,12 @@
"process_editor.configuration_panel.edit_policy_alert_message": "Du må ha tilgangsregler som dekker alle oppgaver. Gå til Tilganger for å sjekke om du har en regel som dekker denne oppgaven. Hvis du ikke har en regel for oppgaven, kan du enten lage en ny regel eller inkludere denne oppgaven i en regel som allerede finnes.",
"process_editor.configuration_panel.edit_policy_open_policy_editor_button": "Gå til Tilganger",
"process_editor.configuration_panel.edit_policy_open_policy_editor_heading": "Åpne Tilganger for å redigere tilgangsregler",
- "process_editor.configuration_panel_actions_action_label": "Handling {{ actionIndex }}: {{ actionName }}",
+ "process_editor.configuration_panel_actions_action_label": "Handling {{actionIndex}}: {{actionName}}",
"process_editor.configuration_panel_actions_action_type_help_text": "Hjelpetekst for valg av handlingstype",
"process_editor.configuration_panel_actions_add_new": "Legg til ny handling",
"process_editor.configuration_panel_actions_combobox_description": "Velg en predefinert handling eller definer din egen ved å skrive inn navnet som fritekst i feltet",
"process_editor.configuration_panel_actions_custom_action": "Skriv en egendefinert handling",
- "process_editor.configuration_panel_actions_delete_action": "Slett {{ actionName }}-handlingen",
+ "process_editor.configuration_panel_actions_delete_action": "Slett {{actionName}}-handlingen",
"process_editor.configuration_panel_actions_set_server_action_info": "Handlingen skal utføres uten å endre status på prosessen. Dette alternativet er kun tilgjengelig for egendefinerte handlinger.",
"process_editor.configuration_panel_actions_set_server_action_label": "Handlingen skal ikke påvirke prosessen",
"process_editor.configuration_panel_actions_title": "Handlinger",
@@ -824,7 +824,7 @@
"process_editor.configuration_panel_select_data_model": "Velg en datamodell",
"process_editor.configuration_panel_set_data_model": "Datamodell:",
"process_editor.configuration_panel_set_data_model_link": "Legg til datamodell",
- "process_editor.configuration_panel_set_data_types_to_sign": "Datyper som skal signeres:",
+ "process_editor.configuration_panel_set_data_types_to_sign": "Datatyper som skal signeres:",
"process_editor.configuration_panel_signing_task": "Oppgave: Signering",
"process_editor.configuration_view_panel_id_label": "ID:",
"process_editor.configuration_view_panel_name_label": "Navn: ",
@@ -848,7 +848,7 @@
"process_editor.sync_error_layout_sets_data_type": "En feil oppsto under synkronisering av datatype i filen 'layoutsets.json'. Vennligst forsikre deg om at 'layoutsets.json' kun inneholder gyldig JSON-struktur og prøv igjen.",
"process_editor.sync_error_layout_sets_task_id": "En feil oppsto under synkronisering av oppgave-ID i filen 'layoutsets.json'. Vennligst forsikre deg om at 'layoutsets.json' kun inneholder gyldig JSON-struktur og prøv igjen.",
"process_editor.sync_error_policy_file_task_id": "En feil oppsto under synkronisering av oppgave-ID i filen 'policy.json'. Vennligst forsikre deg om at 'policy.json' kun inneholder gyldig JSON-struktur og prøv igjen.",
- "process_editor.too_old_version_helptext_content": "Du har nå versjon {{ version }} av app-biblioteket vårt.\n\nVi lanserer muligheten til å redigere prosessen sammen med versjon 8 av biblioteket. Når du har oppgradert til versjon 8, får du funksjonalitet for å redigere prosessen.\n\nFør det kan du bare se prosessen og eventuelle oppsett som er knyttet til den.",
+ "process_editor.too_old_version_helptext_content": "Du har nå versjon {{version}} av app-biblioteket vårt.\n\nVi lanserer muligheten til å redigere prosessen sammen med versjon 8 av biblioteket. Når du har oppgradert til versjon 8, får du funksjonalitet for å redigere prosessen.\n\nFør det kan du bare se prosessen og eventuelle oppsett som er knyttet til den.",
"process_editor.too_old_version_helptext_title": "Informasjon om hvorfor prosessen ikke kan redigeres",
"process_editor.too_old_version_title": "Prosessen kan ikke redigeres",
"process_editor.unknown_heading_error_message": "Obs, noe gikk galt!",
diff --git a/frontend/packages/process-editor/src/bpmnProviders/SupportedPaletteProvider.js b/frontend/packages/process-editor/src/bpmnProviders/SupportedPaletteProvider.js
index 62c359405a5..2d217ed88a9 100644
--- a/frontend/packages/process-editor/src/bpmnProviders/SupportedPaletteProvider.js
+++ b/frontend/packages/process-editor/src/bpmnProviders/SupportedPaletteProvider.js
@@ -163,6 +163,7 @@ class SupportedPaletteProvider {
className: 'bpmn-icon-task-generic bpmn-icon-data-task',
title: translate('Create Altinn Data Task'),
action: {
+ click: createCustomTask('data'),
dragstart: createCustomTask('data'),
},
},
@@ -171,6 +172,7 @@ class SupportedPaletteProvider {
title: translate('Create Altinn Feedback Task'),
className: 'bpmn-icon-task-generic bpmn-icon-feedback-task',
action: {
+ click: createCustomTask('feedback'),
dragstart: createCustomTask('feedback'),
},
},
@@ -179,6 +181,7 @@ class SupportedPaletteProvider {
className: 'bpmn-icon-task-generic bpmn-icon-signing-task',
title: translate('Create Altinn signing Task'),
action: {
+ click: createCustomSigningTask(),
dragstart: createCustomSigningTask(),
},
},
@@ -187,6 +190,7 @@ class SupportedPaletteProvider {
className: 'bpmn-icon-task-generic bpmn-icon-confirmation-task',
title: translate('Create Altinn Confirm Task'),
action: {
+ click: createCustomConfirmationTask(),
dragstart: createCustomConfirmationTask(),
},
},
@@ -195,6 +199,7 @@ class SupportedPaletteProvider {
className: `bpmn-icon-task-generic ${shouldDisplayFeature('displayPaymentTaskProcessEditor') ? 'bpmn-icon-payment-task' : 'payment-is-hidden-based-on-feature-toggle'}`,
title: translate('Payment'),
action: {
+ click: createCustomPaymentTask(),
dragstart: createCustomPaymentTask(),
},
},
diff --git a/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.module.css b/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.module.css
index a439a965053..84394056058 100644
--- a/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.module.css
+++ b/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.module.css
@@ -1,14 +1,3 @@
-.container {
- visibility: hidden;
-}
-
-.spinner {
- display: flex;
- flex: 1;
- justify-content: center;
- align-content: center;
-}
-
.editorContainer {
border: 1px solid var(--fds-semantic-border-neutral-default);
border-radius: 5px;
diff --git a/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.test.tsx b/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.test.tsx
index 3a9a6a3f80d..4d632932795 100644
--- a/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.test.tsx
+++ b/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.test.tsx
@@ -13,11 +13,6 @@ jest.mock('../../../hooks/useBpmnEditor', () => ({
describe('BPMNEditor', () => {
afterEach(jest.clearAllMocks);
- it('render spinner when pendingApiOperations is true', () => {
- renderBpmnEditor({ pendingApiOperations: true });
-
- screen.getByText(textMock('process_editor.loading'));
- });
it('does not render spinner when pendingApiOperations is false', () => {
renderBpmnEditor({ pendingApiOperations: false });
diff --git a/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.tsx b/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.tsx
index a93484c9aa1..6a3b9bc2a06 100644
--- a/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.tsx
+++ b/frontend/packages/process-editor/src/components/Canvas/BPMNEditor/BPMNEditor.tsx
@@ -1,28 +1,10 @@
import React from 'react';
import classes from './BPMNEditor.module.css';
import { useBpmnEditor } from '../../../hooks/useBpmnEditor';
-
import './BPMNEditor.css';
-import { useBpmnApiContext } from '../../../contexts/BpmnApiContext';
-import { StudioSpinner } from '@studio/components';
-import { useTranslation } from 'react-i18next';
export const BPMNEditor = (): React.ReactElement => {
- const { t } = useTranslation();
const { canvasRef } = useBpmnEditor();
- const { pendingApiOperations } = useBpmnApiContext();
- return (
- <>
- {pendingApiOperations && (
-
-
-
- )}
-
- >
- );
+ return ;
};
diff --git a/frontend/packages/process-editor/src/components/Canvas/Canvas.tsx b/frontend/packages/process-editor/src/components/Canvas/Canvas.tsx
index f78777025eb..b851ba92693 100644
--- a/frontend/packages/process-editor/src/components/Canvas/Canvas.tsx
+++ b/frontend/packages/process-editor/src/components/Canvas/Canvas.tsx
@@ -11,14 +11,12 @@ import { BPMNEditor } from './BPMNEditor';
import { VersionHelpText } from './VersionHelpText';
export const Canvas = (): React.ReactElement => {
- const { isEditAllowed, bpmnXml } = useBpmnContext();
+ const { isEditAllowed } = useBpmnContext();
return (
<>
{!isEditAllowed && }
-
- {isEditAllowed ? : }
-
+ {isEditAllowed ? : }
>
);
};
diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.tsx
index 83d7ffb1fa7..7364d1e84d5 100644
--- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.tsx
+++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditActions/EditActions.tsx
@@ -30,7 +30,7 @@ export const EditActions = () => {
<>
{actionElements.map((actionElement: ModdleElement, index: number) => (
{
+ const elementSelector = `${type}[data-element-id="${id}"]`;
+ await this.page.waitForSelector(elementSelector);
+ return elementSelector;
+ }
+}
diff --git a/frontend/testing/playwright/pages/DataModelPage.ts b/frontend/testing/playwright/pages/DataModelPage.ts
index 2a49e4c7049..ca30b27f387 100644
--- a/frontend/testing/playwright/pages/DataModelPage.ts
+++ b/frontend/testing/playwright/pages/DataModelPage.ts
@@ -113,11 +113,10 @@ export class DataModelPage extends BasePage {
}
public async checkThatSuccessAlertIsVisibleOnScreen(): Promise {
- await this.page
- .getByRole('alert', {
- name: this.textMock('schema_editor.data_model_generation_success_message'),
- })
- .isVisible();
+ const alert = this.page.getByText(
+ this.textMock('schema_editor.data_model_generation_success_message'),
+ );
+ await expect(alert).toBeVisible();
}
public async checkThatDataModelOptionExists(option: string): Promise {
diff --git a/frontend/testing/playwright/pages/GiteaPage.ts b/frontend/testing/playwright/pages/GiteaPage.ts
index e9e62a6b687..f18f06b712b 100644
--- a/frontend/testing/playwright/pages/GiteaPage.ts
+++ b/frontend/testing/playwright/pages/GiteaPage.ts
@@ -2,6 +2,8 @@ import type { LanguageCode } from '../enum/LanguageCode';
import { BasePage } from '../helpers/BasePage';
import type { Environment } from '../helpers/StudioEnvironment';
import type { Page } from '@playwright/test';
+import { type BpmnTaskType } from '../types/BpmnTaskType';
+import { expect } from '@playwright/test';
// Since this page is Gitea's page, it's not using the nb/en.json files, which are used in the frontend.
const giteaPageTexts: Record = {
@@ -12,6 +14,8 @@ const giteaPageTexts: Record = {
dataModelBindings: 'dataModelBindings',
config: 'config',
texts: 'texts',
+ process: 'process',
+ applicationmetadata: 'applicationmetadata',
};
export class GiteaPage extends BasePage {
@@ -105,4 +109,106 @@ export class GiteaPage extends BasePage {
public async verifyTextIdAndValue(id: string, value: string): Promise {
await this.page.getByText(`"id": "${id}", "value": "${value}"`, { exact: true }).isVisible();
}
+
+ public async clickOnProcessFilesButton(): Promise {
+ await this.page.getByRole('link', { name: giteaPageTexts['process'], exact: true }).click();
+ }
+
+ public async clickOnProcessBpmnFile(): Promise {
+ await this.page.getByRole('link', { name: `${giteaPageTexts['process']}.bpmn` }).click();
+ }
+
+ public async verifyThatTheNewTaskIsVisible(id: string, task: BpmnTaskType): Promise {
+ const text = this.page.getByText(``);
+ await expect(text).toBeVisible();
+ }
+
+ public async verifyThatTheNewTaskIsHidden(id: string, task: BpmnTaskType): Promise {
+ await this.page.getByText(``).isHidden();
+ }
+
+ public async verifySequenceFlowDirection(fromId: string, toId: string): Promise {
+ const firstPartOfText = this.page.getByText('`);
+ await expect(secondPartOfText).toBeVisible();
+ }
+
+ public async clickOnApplicationMetadataFile(): Promise {
+ await this.page
+ .getByRole('link', { name: `${giteaPageTexts['applicationmetadata']}.json` })
+ .click();
+ }
+
+ public async verifyIdInDataModel(id: string, dataModel: string): Promise {
+ const text = `
+ "id": "${dataModel}",
+ "allowedContentTypes": [
+ "application/xml"
+ ],
+ "appLogic": {
+ "autoCreate": true,
+ "classRef": "Altinn.App.Models.${dataModel}.${dataModel}",
+ "allowAnonymousOnStateless": false,
+ "autoDeleteOnProcessEnd": false
+ },
+ "taskId": "${id}",
+ "maxCount": 1,
+ "minCount": 1,
+ "enablePdfCreation": true,
+ "enableFileScan": false,
+ "validationErrorOnPendingFileScan": false,
+ "enabledFileAnalysers": [],
+ "enabledFileValidators": []
+ `;
+ const textLocator = this.page.getByText(text);
+ expect(textLocator).toBeVisible();
+ }
+
+ public async verifyThatActionIsVisible(action: string): Promise {
+ await this.page.getByText(`${action}`).isVisible();
+ }
+
+ public async verifyThatActionIsHidden(action: string): Promise {
+ await this.page.getByText(`${action}`).isHidden();
+ }
+
+ public async verifyThatTaskIsHidden(task: string): Promise {
+ await this.page.getByText(`${task}`).isHidden();
+ }
+
+ public async verifyThatTaskIsVisible(task: string): Promise {
+ await this.page.getByText(`${task}`).isVisible();
+ }
+
+ public async verifyThatDataTypeToSignIsHidden(dataTypeToSign: string): Promise {
+ const text = `
+
+
+ ${dataTypeToSign}
+
+
+ `;
+ await this.page.getByText(text).isHidden();
+ }
+
+ public async verifyThatDataTypeToSignIsVisible(dataTypeToSign: string): Promise {
+ const text = `
+
+
+ ${dataTypeToSign}
+
+
+ `;
+ await this.page.getByText(text).isVisible();
+ }
+
+ public async verifyThatCustomReceiptIsNotVisible(): Promise {
+ await this.page.getByText('"taskId": "CustomReceipt"').isHidden();
+ }
+
+ public async verifyThatCustomReceiptIsVisible(): Promise {
+ await this.page.getByText('"taskId": "CustomReceipt"').isVisible();
+ }
}
diff --git a/frontend/testing/playwright/pages/ProcessEditorPage.ts b/frontend/testing/playwright/pages/ProcessEditorPage.ts
index 11eb5c5db8a..8c06dacaf3f 100644
--- a/frontend/testing/playwright/pages/ProcessEditorPage.ts
+++ b/frontend/testing/playwright/pages/ProcessEditorPage.ts
@@ -1,6 +1,10 @@
import { BasePage } from '../helpers/BasePage';
import type { Environment } from '../helpers/StudioEnvironment';
import type { Page } from '@playwright/test';
+import { expect } from '@playwright/test';
+import { type BpmnTaskType } from '../types/BpmnTaskType';
+
+const connectionArrowText: string = 'Connect using Sequence/MessageFlow or Association';
export class ProcessEditorPage extends BasePage {
constructor(page: Page, environment?: Environment) {
@@ -14,4 +18,471 @@ export class ProcessEditorPage extends BasePage {
public async verifyProcessEditorPage(): Promise {
await this.page.waitForURL(this.getRoute('editorProcess'));
}
+
+ public async clickOnTaskInBpmnEditor(elementSelector: string): Promise {
+ await this.page.click(elementSelector);
+ }
+
+ public async waitForInitialTaskHeaderToBeVisible(): Promise {
+ const heading = this.page.getByRole('heading', {
+ name: this.textMock('process_editor.configuration_panel_data_task'),
+ });
+
+ await expect(heading).toBeVisible();
+ }
+
+ public async clickOnDataModelButton(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_set_data_model'),
+ })
+ .click();
+ }
+
+ public async waitForDataModelComboboxToBeVisible(): Promise {
+ const combobox = this.page.getByRole('combobox', {
+ name: this.textMock('process_editor.configuration_panel_set_data_model'),
+ });
+ await expect(combobox).toBeVisible();
+ }
+
+ public async clickOnDeleteDataModel(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('general.delete'),
+ })
+ .click();
+ }
+
+ public async waitForAddDataModelButtonToBeVisible(): Promise {
+ const button = this.page.getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_set_data_model_link'),
+ });
+ await expect(button).toBeVisible();
+ }
+
+ public async clickOnAddDataModel(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_set_data_model_link'),
+ })
+ .click();
+ }
+
+ public async clickOnDataModelCombobox(): Promise {
+ await this.page
+ .getByRole('combobox', {
+ name: this.textMock('process_editor.configuration_panel_set_data_model'),
+ })
+ .click();
+ }
+
+ public async clickOnDataModelOption(option: string): Promise {
+ await this.page.getByRole('option', { name: option }).click();
+ }
+
+ public async waitForDataModelButtonToBeVisible(): Promise {
+ const button = this.page.getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_set_data_model'),
+ });
+ await expect(button).toBeVisible();
+ }
+
+ public async verifyDataModelButtonTextIsSelectedDataModel(option: string): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_set_data_model') + option,
+ })
+ .isVisible();
+ }
+
+ public async verifyThatAddNewDataModelButtonIsHidden(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_set_data_model_link'),
+ })
+ .isHidden();
+ }
+
+ public async clickOnActionsAccordion(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_actions_title'),
+ })
+ .click();
+ }
+
+ public async waitForAddActionsButtonToBeVisible(): Promise {
+ const button = this.page.getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_actions_add_new'),
+ });
+ await expect(button).toBeVisible();
+ }
+
+ public async clickAddActionsButton(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_actions_add_new'),
+ })
+ .click();
+ }
+
+ public async waitForActionComboboxTitleToBeVisible(
+ actionIndex: string,
+ actionName?: string,
+ ): Promise {
+ const combobox = this.page.getByRole('combobox', {
+ name: this.textMock('process_editor.configuration_panel_actions_action_label', {
+ actionIndex,
+ actionName: actionName ?? '',
+ }),
+ });
+ await expect(combobox).toBeVisible();
+ }
+
+ public async clickOnActionCombobox(actionIndex: string, actionName?: string): Promise {
+ await this.page
+ .getByRole('combobox', {
+ name: this.textMock('process_editor.configuration_panel_actions_action_label', {
+ actionIndex,
+ actionName: actionName ?? '',
+ }),
+ })
+ .click();
+ }
+
+ public async clickOnActionOption(action: string): Promise {
+ await this.page.getByRole('option', { name: action }).click();
+ }
+
+ public async removeFocusFromActionCombobox(
+ actionIndex: string,
+ actionName?: string,
+ ): Promise {
+ await this.page
+ .getByRole('combobox', {
+ name: this.textMock('process_editor.configuration_panel_actions_action_label', {
+ actionIndex,
+ actionName: actionName ?? '',
+ }),
+ })
+ .blur();
+ }
+
+ public async clickOnSaveActionButton(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('general.save'),
+ })
+ .click();
+ }
+
+ public async waitForActionButtonToBeVisible(
+ actionIndex: string,
+ actionName?: string,
+ ): Promise {
+ const button = this.page.getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_actions_action_label', {
+ actionIndex,
+ actionName: actionName ?? '',
+ }),
+ });
+ await expect(button).toBeVisible();
+ }
+
+ public async typeValueInActionCombobox(
+ customText: string,
+ actionIndex: string,
+ actionName?: string,
+ ): Promise {
+ await this.page
+ .getByRole('combobox', {
+ name: this.textMock('process_editor.configuration_panel_actions_action_label', {
+ actionIndex,
+ actionName: actionName ?? '',
+ }),
+ })
+ .fill(customText);
+ }
+
+ public async verifyThatCustomActionTextIsVisible(): Promise {
+ const text = this.page.getByText(
+ this.textMock('process_editor.configuration_panel_actions_custom_action'),
+ );
+ await expect(text).toBeVisible();
+ }
+
+ public async clickOnPolicyAccordion(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_policy_title'),
+ })
+ .click();
+ }
+
+ public async waitForNavigateToPolicyButtonIsVisible(): Promise {
+ const button = this.page.getByRole('button', {
+ name: this.textMock(
+ 'process_editor.configuration_panel.edit_policy_open_policy_editor_button',
+ ),
+ });
+ await expect(button).toBeVisible();
+ }
+
+ public async clickOnNavigateToPolicyEditorButton(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock(
+ 'process_editor.configuration_panel.edit_policy_open_policy_editor_button',
+ ),
+ })
+ .click();
+ }
+
+ public async waitForPolicyEditorModalTabToBeVisible(): Promise {
+ const heading = this.page.getByRole('heading', {
+ name: this.textMock('policy_editor.rules'),
+ level: 2,
+ });
+ await expect(heading).toBeVisible();
+ }
+
+ public async dragTaskInToBpmnEditor(
+ task: BpmnTaskType,
+ dropElementSelector: string,
+ extraDistanceX?: number,
+ extraDistanceY?: number,
+ ) {
+ const boundingBox = await this.page.locator(dropElementSelector).boundingBox();
+ const targetX = boundingBox.width / 2 + (extraDistanceX ?? 0);
+ const targetY = boundingBox.y + boundingBox.height / 2 + (extraDistanceY ?? 0);
+
+ const title = `Create Altinn ${task} task`;
+ await this.startDragElement(title);
+ await this.stopDragElement(targetX, targetY);
+ }
+
+ public async waitForTaskToBeVisibleInConfigPanel(task: BpmnTaskType): Promise {
+ const text = this.page.getByText(`Navn: Altinn ${task} task`);
+ await expect(text).toBeVisible();
+ }
+
+ public async getTaskIdFromOpenNewlyAddedTask(): Promise {
+ const selector = 'text=ID: Activity_';
+ await this.page.waitForSelector(selector);
+ return await this.getFullIdFromButtonSelector(selector);
+ }
+
+ public async clickOnTaskIdEditButton(id: string): Promise {
+ await this.page
+ .getByText(`${this.textMock('process_editor.configuration_panel_id_label')} ${id}`)
+ .click();
+ }
+
+ public async waitForEditIdInputFieldToBeVisible(): Promise {
+ const inputField = this.page.getByRole('textbox', {
+ name: this.textMock('process_editor.configuration_panel_change_task_id'),
+ });
+ await expect(inputField).toBeVisible();
+ }
+
+ public async emptyIdInputfield(): Promise {
+ await this.page
+ .getByRole('textbox', {
+ name: this.textMock('process_editor.configuration_panel_change_task_id'),
+ })
+ .clear();
+ }
+
+ public async writeNewId(id: string): Promise {
+ await this.page
+ .getByRole('textbox', {
+ name: this.textMock('process_editor.configuration_panel_change_task_id'),
+ })
+ .fill(id);
+ }
+
+ public async waitForTextBoxToHaveValue(id: string): Promise {
+ const textBox = this.page.getByRole('textbox', {
+ name: this.textMock('process_editor.configuration_panel_change_task_id'),
+ });
+ await expect(textBox).toHaveValue(id);
+ }
+
+ public async saveNewId(): Promise {
+ await this.page
+ .getByRole('textbox', {
+ name: this.textMock('process_editor.configuration_panel_change_task_id'),
+ })
+ .blur();
+ }
+
+ public async waitForNewTaskIdButtonToBeVisible(id: string): Promise {
+ const button = this.page.getByText(
+ `${this.textMock('process_editor.configuration_panel_id_label')} ${id}`,
+ );
+ await expect(button).toBeVisible();
+ }
+
+ public async verifyThatThereAreNoDataModelsAvailable(): Promise {
+ const noDataModelMessage = this.page.getByText(
+ this.textMock('process_editor.configuration_panel_no_data_model_to_select'),
+ );
+ await expect(noDataModelMessage).toBeVisible();
+ }
+
+ public async pressEscapeOnKeyboard(): Promise {
+ await this.page.keyboard.press('Escape');
+ }
+
+ public async clickOnConnectionArrow(): Promise {
+ await this.page.getByTitle(connectionArrowText).click();
+ }
+
+ public async verifyThatPolicyEditorIsOpen(): Promise {
+ const heading = this.page.getByRole('heading', {
+ name: this.textMock('policy_editor.rules'),
+ level: 2,
+ });
+ await expect(heading).toBeVisible();
+ }
+ public async closePolicyEditor(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('settings_modal.close_button_label'),
+ })
+ .click();
+ }
+
+ public async verifyThatPolicyEditorIsClosed(): Promise {
+ const heading = this.page.getByRole('heading', {
+ name: this.textMock('policy_editor.rules'),
+ level: 2,
+ });
+ await expect(heading).toBeHidden();
+ }
+
+ public async clickDataTypesToSignCombobox(): Promise {
+ await this.page
+ .getByRole('combobox', {
+ name: this.textMock('process_editor.configuration_panel_set_data_types_to_sign'),
+ })
+ .click();
+ }
+
+ public async clickOnDataTypesToSignOption(option: string): Promise {
+ await this.page.getByRole('option', { name: option }).click();
+ }
+
+ public async waitForDataTypeToSignButtonToBeVisible(option: string): Promise {
+ const button = this.page.getByLabel(this.textMock('general.delete_item', { item: option }));
+ await expect(button).toBeVisible();
+ }
+
+ public async waitForEndEventHeaderToBeVisible(): Promise {
+ const heading = this.page.getByRole('heading', {
+ name: this.textMock('process_editor.configuration_panel_end_event'),
+ });
+
+ await expect(heading).toBeVisible();
+ }
+
+ public async clickOnReceiptAccordion(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_custom_receipt_accordion_header'),
+ })
+ .click();
+ }
+
+ public async waitForCreateCustomReceiptButtonToBeVisible(): Promise {
+ const text = this.page.getByRole('button', {
+ name: this.textMock(
+ 'process_editor.configuration_panel_custom_receipt_create_your_own_button',
+ ),
+ });
+ await expect(text).toBeVisible();
+ }
+
+ public async clickOnCreateCustomReceipt(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock(
+ 'process_editor.configuration_panel_custom_receipt_create_your_own_button',
+ ),
+ })
+ .click();
+ }
+
+ public async waitForLayoutTextfieldToBeVisible(): Promise {
+ const textbox = this.page.getByRole('textbox', {
+ name: this.textMock('process_editor.configuration_panel_custom_receipt_textfield_label'),
+ });
+ await expect(textbox).toBeVisible();
+ }
+
+ public async writeLayoutSetId(layoutSetId: string): Promise {
+ await this.page
+ .getByRole('textbox', {
+ name: this.textMock('process_editor.configuration_panel_custom_receipt_textfield_label'),
+ })
+ .fill(layoutSetId);
+ }
+
+ public async clickOnAddDataModelCombobox(): Promise {
+ await this.page
+ .getByRole('combobox', {
+ name: this.textMock(
+ 'process_editor.configuration_panel_custom_receipt_select_data_model_label',
+ ),
+ })
+ .click();
+ }
+
+ public async waitForSaveNewCustomReceiptButtonToBeVisible(): Promise {
+ const button = this.page.getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_custom_receipt_create_button'),
+ });
+ await expect(button).toBeVisible();
+ }
+
+ public async clickOnSaveNewCustomReceiptButton(): Promise {
+ await this.page
+ .getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_custom_receipt_create_button'),
+ })
+ .click();
+ }
+
+ public async waitForEditLayoutSetIdButtonToBeVisible(): Promise {
+ const button = this.page.getByRole('button', {
+ name: this.textMock('process_editor.configuration_panel_custom_receipt_textfield_label'),
+ });
+ await expect(button).toBeVisible();
+ }
+
+ /**
+ *
+ * Helper methods below this
+ *
+ */
+
+ private async startDragElement(title: string): Promise {
+ await this.page.getByTitle(title).hover();
+ await this.page.mouse.down();
+ }
+
+ private async stopDragElement(xPosition: number, yPosition: number): Promise {
+ const numberOfMouseMoveEvents: number = 20;
+ await this.page.mouse.move(xPosition, yPosition, { steps: numberOfMouseMoveEvents });
+ await this.page.mouse.up();
+ }
+
+ private async getFullIdFromButtonSelector(selector: string): Promise {
+ const button = this.page.locator(selector);
+ const fullText = await button.textContent();
+ const extractedText = fullText.match(/ID: (Activity_\w+)/);
+ const fullId: string = extractedText[1];
+ return fullId;
+ }
}
diff --git a/frontend/testing/playwright/playwright.config.ts b/frontend/testing/playwright/playwright.config.ts
index db3d1018f02..66919c36800 100644
--- a/frontend/testing/playwright/playwright.config.ts
+++ b/frontend/testing/playwright/playwright.config.ts
@@ -118,6 +118,18 @@ export default defineConfig({
headless: true,
},
},
+ {
+ name: TestNames.PROCESS_EDITOR,
+ dependencies: [TestNames.SETUP],
+ testDir: './tests/process-editor/',
+ testMatch: '*.spec.ts',
+ use: {
+ ...devices['Desktop Chrome'],
+ storageState: '.playwright/auth/user.json',
+ testAppName: AppNames.PROCESS_EDITOR_APP,
+ headless: true,
+ },
+ },
{
name: TestNames.LOGOUT_AND_INVALID_LOGIN_ONLY,
// Add ALL other test names here to make sure that the log out test is the last test to be executed
@@ -131,6 +143,7 @@ export default defineConfig({
TestNames.UI_EDITOR,
TestNames.SETTINGS_MODAL,
TestNames.TEXT_EDITOR,
+ TestNames.PROCESS_EDITOR,
...Object.values(TestNames).filter(
(testName) => testName !== TestNames.LOGOUT_AND_INVALID_LOGIN_ONLY,
),
diff --git a/frontend/testing/playwright/tests/process-editor/process-editor.spec.ts b/frontend/testing/playwright/tests/process-editor/process-editor.spec.ts
new file mode 100644
index 00000000000..f11eae522f0
--- /dev/null
+++ b/frontend/testing/playwright/tests/process-editor/process-editor.spec.ts
@@ -0,0 +1,363 @@
+import { test } from '../../extenders/testExtend';
+import { expect } from '@playwright/test';
+import type { Page } from '@playwright/test';
+import { Gitea } from '../../helpers/Gitea';
+import { DesignerApi } from '../../helpers/DesignerApi';
+import type { StorageState } from '../../types/StorageState';
+import { ProcessEditorPage } from '../../pages/ProcessEditorPage';
+import { BpmnJSQuery } from '../../helpers/BpmnJSQuery';
+import { Header } from '../../components/Header';
+import { DataModelPage } from '../../pages/DataModelPage';
+import { GiteaPage } from '../../pages/GiteaPage';
+import { type BpmnTaskType } from '../../types/BpmnTaskType';
+
+// This line must be there to ensure that the tests do not run in parallell, and
+// that the before all call is being executed before we start the tests
+test.describe.configure({ mode: 'serial' });
+
+test.beforeAll(async ({ testAppName, request, storageState }) => {
+ const designerApi = new DesignerApi({ app: testAppName });
+ const response = await designerApi.createApp(request, storageState as StorageState);
+ expect(response.ok()).toBeTruthy();
+});
+
+test.afterAll(async ({ request, testAppName }) => {
+ const gitea = new Gitea();
+ const response = await request.delete(gitea.getDeleteAppEndpoint({ app: testAppName }));
+ expect(response.ok()).toBeTruthy();
+});
+
+const setupAndVerifyProcessEditorPage = async (
+ page: Page,
+ testAppName: string,
+): Promise => {
+ const processEditorPage = new ProcessEditorPage(page, { app: testAppName });
+ await processEditorPage.loadProcessEditorPage();
+ await processEditorPage.verifyProcessEditorPage();
+ return processEditorPage;
+};
+
+test('That it is possible to add and remove datamodel, and add actions to the default task in the process editor', async ({
+ page,
+ testAppName,
+}): Promise => {
+ const processEditorPage = await setupAndVerifyProcessEditorPage(page, testAppName);
+ const bpmnJSQuery = new BpmnJSQuery(page);
+ const header = new Header(page, { app: testAppName });
+ const giteaPage = new GiteaPage(page, { app: testAppName });
+
+ const initialTaskDataElementIdSelector: string = await bpmnJSQuery.getTaskByIdAndType(
+ 'Task_1',
+ 'g',
+ );
+ await processEditorPage.clickOnTaskInBpmnEditor(initialTaskDataElementIdSelector);
+ await processEditorPage.waitForInitialTaskHeaderToBeVisible();
+
+ // --------------------- Add and delete datamodel ---------------------
+ await processEditorPage.clickOnDataModelButton();
+ await processEditorPage.waitForDataModelComboboxToBeVisible();
+
+ await processEditorPage.clickOnDeleteDataModel();
+ await processEditorPage.waitForAddDataModelButtonToBeVisible();
+
+ await processEditorPage.clickOnAddDataModel();
+ await processEditorPage.waitForDataModelComboboxToBeVisible();
+
+ const dataModelName: string = 'model';
+ await processEditorPage.clickOnDataModelCombobox();
+ await processEditorPage.clickOnDataModelOption(dataModelName);
+ await processEditorPage.waitForDataModelButtonToBeVisible();
+
+ await processEditorPage.verifyDataModelButtonTextIsSelectedDataModel(dataModelName);
+ await processEditorPage.verifyThatAddNewDataModelButtonIsHidden();
+
+ // --------------------- Add actions ---------------------
+ await processEditorPage.clickOnActionsAccordion();
+ await processEditorPage.waitForAddActionsButtonToBeVisible();
+
+ const actionIndex1: string = '1';
+ await processEditorPage.clickAddActionsButton();
+ await processEditorPage.waitForActionComboboxTitleToBeVisible(actionIndex1);
+
+ const actionOptionWrite: string = 'write';
+ await processEditorPage.clickOnActionCombobox(actionIndex1);
+ await processEditorPage.clickOnActionOption(actionOptionWrite);
+ await processEditorPage.removeFocusFromActionCombobox(actionIndex1);
+ await processEditorPage.clickOnSaveActionButton();
+ await processEditorPage.waitForActionButtonToBeVisible(actionIndex1, actionOptionWrite);
+
+ // --------------------- Verify policy editor ---------------------
+ await processEditorPage.clickOnPolicyAccordion();
+ await processEditorPage.waitForNavigateToPolicyButtonIsVisible();
+ await processEditorPage.clickOnNavigateToPolicyEditorButton();
+
+ await processEditorPage.verifyThatPolicyEditorIsOpen();
+ await processEditorPage.closePolicyEditor();
+ await processEditorPage.verifyThatPolicyEditorIsClosed();
+
+ // --------------------- Check that files are uploaded to Gitea ---------------------
+ await goToGiteaAndNavigateToProcessBpmnFile(header, giteaPage);
+
+ const numberOfPagesBackToAltinnStudio: number = 5;
+ await giteaPage.goBackNPages(numberOfPagesBackToAltinnStudio);
+
+ await processEditorPage.verifyProcessEditorPage();
+ await commitAndPushToGitea(header);
+
+ await goToGiteaAndNavigateToProcessBpmnFile(header, giteaPage);
+ await giteaPage.verifyThatActionIsVisible(actionOptionWrite);
+});
+
+test('That it is possible to add a new task to the process editor, configure some of its data', async ({
+ page,
+ testAppName,
+}): Promise => {
+ const processEditorPage = await setupAndVerifyProcessEditorPage(page, testAppName);
+ const bpmnJSQuery = new BpmnJSQuery(page);
+ const header = new Header(page, { app: testAppName });
+ const dataModelPage = new DataModelPage(page, { app: testAppName });
+ const giteaPage = new GiteaPage(page, { app: testAppName });
+
+ // --------------------- Drag new task into the editor ---------------------
+ const svgSelector = await bpmnJSQuery.getTaskByIdAndType('SingleDataTask', 'svg');
+ const dataTask: BpmnTaskType = 'data';
+ await processEditorPage.dragTaskInToBpmnEditor(dataTask, svgSelector);
+ await processEditorPage.waitForTaskToBeVisibleInConfigPanel(dataTask);
+ const randomGeneratedId = await processEditorPage.getTaskIdFromOpenNewlyAddedTask();
+
+ // --------------------- Edit the id ---------------------
+ const newId: string = 'my_new_id';
+ await editRandomGeneratedId(processEditorPage, randomGeneratedId, newId);
+
+ // --------------------- Add new data model ---------------------
+ await processEditorPage.clickOnAddDataModel();
+ await processEditorPage.waitForDataModelComboboxToBeVisible();
+ await processEditorPage.clickOnDataModelCombobox();
+ await processEditorPage.verifyThatThereAreNoDataModelsAvailable();
+ await processEditorPage.pressEscapeOnKeyboard();
+
+ const newDataModel: string = 'testDataModel';
+ await navigateToDataModelAndCreateNewDataModel(
+ dataModelPage,
+ processEditorPage,
+ header,
+ newDataModel,
+ );
+ const newTaskSelector: string = await bpmnJSQuery.getTaskByIdAndType(newId, 'g');
+ await processEditorPage.clickOnTaskInBpmnEditor(newTaskSelector);
+
+ await processEditorPage.clickOnAddDataModel();
+ await processEditorPage.waitForDataModelComboboxToBeVisible();
+ await processEditorPage.clickOnDataModelCombobox();
+ await processEditorPage.clickOnDataModelOption(newDataModel);
+ await processEditorPage.waitForDataModelButtonToBeVisible();
+ await processEditorPage.verifyDataModelButtonTextIsSelectedDataModel(newDataModel);
+
+ // --------------------- Connect the task to the process ---------------------
+ await processEditorPage.clickOnConnectionArrow();
+
+ const initialId: string = 'Task_1';
+ const initialTaskSelector: string = await bpmnJSQuery.getTaskByIdAndType(initialId, 'g');
+ await processEditorPage.clickOnTaskInBpmnEditor(initialTaskSelector);
+
+ // --------------------- Check that files are uploaded to Gitea ---------------------
+ await goToGiteaAndNavigateToProcessBpmnFile(header, giteaPage);
+ await giteaPage.verifyThatTheNewTaskIsHidden(newId, dataTask);
+
+ const numberOfPagesBackToAltinnStudio: number = 5;
+ await giteaPage.goBackNPages(numberOfPagesBackToAltinnStudio);
+
+ await processEditorPage.verifyProcessEditorPage();
+ await commitAndPushToGitea(header);
+
+ await goToGiteaAndNavigateToProcessBpmnFile(header, giteaPage);
+ await giteaPage.verifyThatTheNewTaskIsVisible(newId, dataTask);
+
+ await giteaPage.verifySequenceFlowDirection(newId, initialId);
+ const numblerBackToConfig: number = 2;
+ await giteaPage.goBackNPages(numblerBackToConfig);
+ await giteaPage.clickOnApplicationMetadataFile();
+ await giteaPage.verifyIdInDataModel(newId, newDataModel);
+});
+
+test('That it is possible to add a new signing task, and update the datatypes to sign', async ({
+ page,
+ testAppName,
+}): Promise => {
+ const processEditorPage = await setupAndVerifyProcessEditorPage(page, testAppName);
+ const bpmnJSQuery = new BpmnJSQuery(page);
+ const header = new Header(page, { app: testAppName });
+ const giteaPage = new GiteaPage(page, { app: testAppName });
+
+ // --------------------- Drag new task into the editor ---------------------
+ const svgSelector = await bpmnJSQuery.getTaskByIdAndType('SingleDataTask', 'svg');
+ const signingTask: BpmnTaskType = 'signing';
+
+ const extraMovingDistanceX: number = -120;
+ const extraMovingDistanceY: number = 0;
+ await processEditorPage.dragTaskInToBpmnEditor(
+ signingTask,
+ svgSelector,
+ extraMovingDistanceX,
+ extraMovingDistanceY,
+ );
+ await processEditorPage.waitForTaskToBeVisibleInConfigPanel(signingTask);
+ const randomGeneratedId = await processEditorPage.getTaskIdFromOpenNewlyAddedTask();
+
+ // --------------------- Edit the id ---------------------
+ const newId: string = 'signing_id';
+ await editRandomGeneratedId(processEditorPage, randomGeneratedId, newId);
+
+ // --------------------- Add data types to sign ---------------------
+ await processEditorPage.clickDataTypesToSignCombobox();
+ const dataTypeToSign: string = 'ref-data-as-pdf';
+ await processEditorPage.clickOnDataTypesToSignOption(dataTypeToSign);
+ await processEditorPage.waitForDataTypeToSignButtonToBeVisible(dataTypeToSign);
+ await processEditorPage.pressEscapeOnKeyboard();
+
+ // --------------------- Verify correct actions ---------------------
+ await processEditorPage.clickOnActionsAccordion();
+
+ const actionIndex1: string = '1';
+ const actionIndex2: string = '2';
+ const actionOptionSign: string = 'sign';
+ const actionOptionReject: string = 'reject';
+ await processEditorPage.waitForActionButtonToBeVisible(actionIndex1, actionOptionSign);
+ await processEditorPage.waitForActionButtonToBeVisible(actionIndex2, actionOptionReject);
+
+ // --------------------- Check that files are uploaded to Gitea ---------------------
+ await goToGiteaAndNavigateToProcessBpmnFile(header, giteaPage);
+ await giteaPage.verifyThatTaskIsHidden(signingTask);
+ await giteaPage.verifyThatActionIsHidden(actionOptionSign);
+ await giteaPage.verifyThatActionIsHidden(actionOptionReject);
+ await giteaPage.verifyThatDataTypeToSignIsHidden(dataTypeToSign);
+
+ const numberOfPagesBackToAltinnStudio: number = 5;
+ await giteaPage.goBackNPages(numberOfPagesBackToAltinnStudio);
+
+ await processEditorPage.verifyProcessEditorPage();
+ await commitAndPushToGitea(header);
+
+ await giteaPage.verifyThatTaskIsVisible(signingTask);
+ await giteaPage.verifyThatActionIsVisible(actionOptionSign);
+ await giteaPage.verifyThatActionIsVisible(actionOptionReject);
+ await giteaPage.verifyThatDataTypeToSignIsVisible(signingTask);
+});
+
+test('That it is possible to create a custom receipt', async ({ page, testAppName }) => {
+ const processEditorPage = await setupAndVerifyProcessEditorPage(page, testAppName);
+ const dataModelPage = new DataModelPage(page, { app: testAppName });
+ const bpmnJSQuery = new BpmnJSQuery(page);
+ const header = new Header(page, { app: testAppName });
+ const giteaPage = new GiteaPage(page, { app: testAppName });
+
+ // --------------------- Create new data model ---------------------
+ const newDataModel: string = 'newDataModel';
+ await navigateToDataModelAndCreateNewDataModel(
+ dataModelPage,
+ processEditorPage,
+ header,
+ newDataModel,
+ );
+
+ // --------------------- Add layout set id and data model id to the receipt ---------------------
+ const endEvent: string = await bpmnJSQuery.getTaskByIdAndType('EndEvent_1', 'g');
+ await processEditorPage.clickOnTaskInBpmnEditor(endEvent);
+ await processEditorPage.waitForEndEventHeaderToBeVisible();
+
+ await processEditorPage.clickOnReceiptAccordion();
+ await processEditorPage.waitForCreateCustomReceiptButtonToBeVisible();
+
+ await processEditorPage.clickOnCreateCustomReceipt();
+ await processEditorPage.waitForLayoutTextfieldToBeVisible();
+
+ const newLayoutSetId: string = 'layoutSetId';
+ await processEditorPage.writeLayoutSetId(newLayoutSetId);
+ await processEditorPage.clickOnAddDataModelCombobox();
+ await processEditorPage.clickOnDataModelOption(newDataModel);
+ await processEditorPage.pressEscapeOnKeyboard();
+
+ await processEditorPage.waitForSaveNewCustomReceiptButtonToBeVisible();
+ await processEditorPage.clickOnSaveNewCustomReceiptButton();
+ await processEditorPage.waitForEditLayoutSetIdButtonToBeVisible();
+
+ // --------------------- Check that files are uploaded to Gitea ---------------------
+ await goToGiteaAndNavigateToApplicationMetadataFile(header, giteaPage);
+ await giteaPage.verifyThatCustomReceiptIsNotVisible();
+ const numberOfPagesBackToAltinnStudio: number = 4;
+ await giteaPage.goBackNPages(numberOfPagesBackToAltinnStudio);
+
+ await processEditorPage.verifyProcessEditorPage();
+ await commitAndPushToGitea(header);
+
+ await goToGiteaAndNavigateToApplicationMetadataFile(header, giteaPage);
+ await giteaPage.verifyThatCustomReceiptIsVisible();
+});
+
+// --------------------- Helper Functions ---------------------
+const editRandomGeneratedId = async (
+ processEditorPage: ProcessEditorPage,
+ randomGeneratedId: string,
+ newId: string,
+): Promise => {
+ await processEditorPage.clickOnTaskIdEditButton(randomGeneratedId);
+ await processEditorPage.waitForEditIdInputFieldToBeVisible();
+ await processEditorPage.emptyIdInputfield();
+ await processEditorPage.writeNewId(newId);
+ await processEditorPage.waitForTextBoxToHaveValue(newId);
+ await processEditorPage.saveNewId();
+ await processEditorPage.waitForNewTaskIdButtonToBeVisible(newId);
+};
+
+const goToGiteaAndNavigateToProcessBpmnFile = async (
+ header: Header,
+ giteaPage: GiteaPage,
+): Promise => {
+ await header.clickOnThreeDotsMenu();
+ await header.clickOnGoToGiteaRepository();
+
+ await giteaPage.verifyGiteaPage();
+ await giteaPage.clickOnAppFilesButton();
+ await giteaPage.clickOnConfigFilesButton();
+ await giteaPage.clickOnProcessFilesButton();
+ await giteaPage.clickOnProcessBpmnFile();
+};
+
+const commitAndPushToGitea = async (header: Header): Promise => {
+ await header.clickOnUploadLocalChangesButton();
+ await header.clickOnValidateChanges();
+ await header.checkThatUploadSuccessMessageIsVisible();
+};
+
+const navigateToDataModelAndCreateNewDataModel = async (
+ dataModelPage: DataModelPage,
+ processEditorPage: ProcessEditorPage,
+ header: Header,
+ newDataModelName: string,
+): Promise => {
+ await header.clickOnNavigateToPageInTopMenuHeader('data_model');
+ await dataModelPage.verifyDataModelPage();
+ await dataModelPage.clickOnCreateNewDataModelButton();
+ await dataModelPage.typeDataModelName(newDataModelName);
+ await dataModelPage.clickOnCreateModelButton();
+ await dataModelPage.waitForDataModelToAppear(newDataModelName);
+ await dataModelPage.clickOnGenerateDataModelButton();
+ await dataModelPage.checkThatSuccessAlertIsVisibleOnScreen();
+ await dataModelPage.waitForSuccessAlertToDisappear();
+
+ await header.clickOnNavigateToPageInTopMenuHeader('process_editor');
+ await processEditorPage.verifyProcessEditorPage();
+};
+
+const goToGiteaAndNavigateToApplicationMetadataFile = async (
+ header: Header,
+ giteaPage: GiteaPage,
+): Promise => {
+ await header.clickOnThreeDotsMenu();
+ await header.clickOnGoToGiteaRepository();
+
+ await giteaPage.verifyGiteaPage();
+ await giteaPage.clickOnAppFilesButton();
+ await giteaPage.clickOnConfigFilesButton();
+ await giteaPage.clickOnApplicationMetadataFile();
+};
diff --git a/frontend/testing/playwright/types/BpmnTaskType.ts b/frontend/testing/playwright/types/BpmnTaskType.ts
new file mode 100644
index 00000000000..484f00b5f36
--- /dev/null
+++ b/frontend/testing/playwright/types/BpmnTaskType.ts
@@ -0,0 +1 @@
+export type BpmnTaskType = 'data' | 'feedback' | 'signing' | 'confirm';