Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions workspaces/orchestrator/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
playwright.config.ts
e2e-tests/
dist-dynamic
dist-scalprum
!.eslintrc.js
Expand Down
3 changes: 2 additions & 1 deletion workspaces/orchestrator/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ site
*.session.sql

# E2E test reports
e2e-test-report/
e2e-test-report*/
test-results/

# Sonataflow Dev Container Temp files
packages/backend/.devModeTemp
282 changes: 282 additions & 0 deletions workspaces/orchestrator/e2e-tests/orchestrator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
/*
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 { test, expect, Page, type BrowserContext } from '@playwright/test';
import { Orchestrator } from './pages/orchestrator';
import { runAccessibilityTests } from './utils/accessibility';
import { OrchestratorHelper } from './utils/helper';
import { OrchestratorMessages, getTranslations } from './utils/translations';

const LOCALE_DISPLAY_NAMES: Record<string, string> = {
en: 'English',
de: 'Deutsch',
es: 'Español',
fr: 'Français',
it: 'Italiano',
ja: '日本語',
};

function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function countHeadingPattern(template: string): RegExp {
const regexBody = template.split('{{count}}').map(escapeRegExp).join('\\d+');
return new RegExp(`^${regexBody}$`);
}

/**
* Get the display name for a locale code
*/
function getLocaleDisplayName(locale: string): string {
const baseLocale = locale.split('-')[0];
return LOCALE_DISPLAY_NAMES[baseLocale] || locale;
}

test.describe('Orchestrator workflow runs', () => {
let orchestrator: Orchestrator;
let orchestratorHelper: OrchestratorHelper;
let translations: OrchestratorMessages;
let sharedPage!: Page;
let sharedContext!: BrowserContext;

async function switchToLocale(page: Page, locale: string): Promise<void> {
const baseLocale = locale.split('-')[0];
if (baseLocale === 'en') return;

const displayName = getLocaleDisplayName(locale);
const localeDisplayPattern = new RegExp(
`^(${Object.values(LOCALE_DISPLAY_NAMES).map(escapeRegExp).join('|')})$`,
);

await page.goto('/settings');
await page.waitForLoadState('networkidle');
const languageButton = page
.getByRole('button', { name: localeDisplayPattern })
.first();
await languageButton.waitFor({ state: 'visible', timeout: 30_000 });
await languageButton.click();
await page.getByRole('option', { name: displayName }).click();
await page.goto('/');
}

test.beforeAll(async ({ browser }) => {
sharedContext = await browser.newContext();
sharedPage = await sharedContext.newPage();
const currentLocale = await sharedPage.evaluate(
() => globalThis.navigator.language.split('-')[0],
);
await sharedPage.goto('/');
await sharedPage.getByRole('button', { name: 'Enter' }).click();
await switchToLocale(sharedPage, currentLocale);
translations = getTranslations(currentLocale);
orchestrator = new Orchestrator(sharedPage, translations, currentLocale);
orchestratorHelper = new OrchestratorHelper(sharedPage, translations);
});

test.beforeEach(async () => {
await orchestrator.navigateToOrchestrator('Orchestrator');
});

test.afterAll(async () => {
if (sharedContext) {
await sharedContext.close();
}
});

test.describe('Orchestrator > Workflow runs page', () => {
test.beforeEach(async () => {
await orchestrator.navigateToWorkflowRunTab(
translations.page.tabs.workflows,
);
});

test('Verify workflow runs table', async ({
browser: _browser,
}, testInfo) => {
await runAccessibilityTests(sharedPage, testInfo);
await orchestratorHelper.verifyTableHeadingAndRows([
translations.table.headers.name,
translations.table.headers.workflowStatus,
translations.table.headers.lastRun,
translations.table.headers.lastRunStatus,
translations.table.headers.description,
translations.table.headers.version,
'Actions',
]);

await orchestratorHelper.searchInputPlaceholder('Hello World workflow');
await expect(
sharedPage
.getByRole('row', { name: 'Hello World workflow' })
.getByRole('button', {
name: translations.table.actions.run,
exact: true,
})
.first(),
).toBeVisible();
await expect(
sharedPage.getByRole('row', { name: 'Hello World workflow' }),
).toContainText(translations.workflow.status.available);
await sharedPage
.getByRole('row', { name: 'Hello World workflow' })
.getByRole('button', {
name: translations.table.actions.viewInputSchema,
})
.click();
const workflowSchema = sharedPage.getByRole('dialog', {
name: /Hello World Workflow input schema/i,
});
await expect(workflowSchema).toBeVisible();
await expect(
workflowSchema.getByText(translations.messages.noInputSchemaWorkflow),
).toBeVisible();
await sharedPage
.getByRole('button', { name: 'close', exact: true })
.click();
});

test('Run Test Object Type Support in ui:props workflow', async ({
browser: _browser,
}) => {
const workflowName = 'Test Object Type Support in ui:props';
const workflowInputs = {
name: 'test-name',
email: 'test@test.com',
simpleText: 'sample testing',
objectExample: '{"kind":"demo","id":42,"tags":["a","b"]}',
};

await orchestrator.runUiPropsWorkflow(workflowName, workflowInputs);

await expect(sharedPage).toHaveURL(/\/orchestrator\/instances\/.+/);
await orchestratorHelper.verifyHeading(
translations.run.pageTitle.replace('{{processName}}', workflowName),
);
await orchestrator.verifyUiPropsWorkflowInstanceDetails(workflowName);
await orchestrator.verifyUiPropsWorkflowRunVariables(workflowInputs);
});

test('Greeting workflow execution and workflow tab validation', async ({
browser: _browser,
}) => {
const workflowName = 'Greeting workflow';

await orchestrator.runGreetingWorkflow(workflowName);
await orchestrator.navigateToOrchestrator('Orchestrator');
await orchestrator.navigateToWorkflowRunTab(
translations.page.tabs.workflows,
);
await orchestrator.searchWorkflow(workflowName);
await orchestrator.validateGreetingWorkflowTableRow(workflowName);
});

test('Greeting workflow run details validation', async ({
browser: _browser,
}) => {
const workflowName = 'Greeting workflow';

await orchestrator.runGreetingWorkflow(workflowName);
await orchestrator.reRunGreetingWorkflow();
await orchestrator.verifyWorkflowRunDetails();
});

test('Sample Retry Test', async ({ browser: _browser }) => {
const workflowName = 'Sample Retry Test';

await orchestrator.runSampleRetryTest(workflowName);
await orchestrator.verifySampleRetryTest();
});
});

test.describe('Orchestrator > All runs page', () => {
test.beforeEach(async () => {
await orchestrator.navigateToWorkflowRunTab(
translations.page.tabs.allRuns,
);
});

test('Verify all runs tab', async ({ browser: _browser }, testInfo) => {
await orchestrator.navigateToWorkflowRunTab(
translations.page.tabs.workflows,
);
await orchestrator.searchWorkflow('Hello World workflow');
await sharedPage
.getByRole('row', { name: 'Hello World workflow' })
.getByRole('button', {
name: translations.table.actions.run,
exact: true,
})
.click();
await orchestrator.submitWorkflowRunFromReview();
await orchestratorHelper.verifyHeading(
translations.run.pageTitle.replace(
'{{processName}}',
'Hello World workflow',
),
);
await orchestrator.navigateToOrchestrator('Orchestrator');
await orchestrator.navigateToWorkflowRunTab(
translations.page.tabs.allRuns,
);
await expect(
sharedPage.getByText(
countHeadingPattern(translations.table.title.allRuns),
),
).toBeVisible();
await runAccessibilityTests(sharedPage, testInfo);
await orchestrator.verifyWorkflowRunTabDetails();
});

// Remove the fixme with the fix of bug https://redhat.atlassian.net/browse/RHDHBUGS-3401
test.fixme('All runs tab workflow details validation', async ({
browser: _browser,
}) => {
await sharedPage
.getByRole('link', { name: 'Hello World workflow' })
.first()
.click();
await orchestratorHelper.verifyHeading('Hello World workflow');
await orchestrator.verifyWorkflowDetails();
await orchestrator.navigateToWorkflowRunTab(
translations.page.tabs.workflowRuns,
);
await orchestrator.verifyWorkflowRunTab();
const runLocator = await sharedPage
.getByText(
countHeadingPattern(translations.table.title.allWorkflowRuns),
)
.textContent();
const runCount = parseInt(runLocator?.match(/\d+/)?.[0] || '0');
await sharedPage
.getByRole('button', {
name: translations.table.actions.run,
exact: true,
})
.first()
.click();
await orchestrator.submitWorkflowRunFromReview();
await orchestratorHelper.verifyHeading(
translations.run.pageTitle.replace(
'{{processName}}',
'Hello World workflow',
),
);
await sharedPage.goto(`/orchestrator/workflows/hello_world/runs`);
await orchestrator.verifyWorkflowRunTab(runCount + 1);
});
});
});
Loading
Loading