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
136 changes: 135 additions & 1 deletion js/apps/admin-ui/test/autentication/flow.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Page, expect } from "@playwright/test";
import { type Locator, type Page, expect } from "@playwright/test";
import { selectItem } from "../utils/form.ts";
import { confirmModal } from "../utils/modal.ts";

Expand Down Expand Up @@ -145,3 +145,137 @@ export async function fillCreateForm(
await selectItem(page, page.getByLabel("Flow type"), type);
await page.getByTestId("create").click();
}

export async function dragExecutionAboveExecution(
page: Page,
sourceExecution: string,
targetExecution: string,
) {
const treeGrid = page.getByRole("treegrid", { name: "Flows" });
const sourceRow = treeGrid
.getByRole("row")
.filter({ hasText: sourceExecution })
.first();
const targetRow = treeGrid
.getByRole("row")
.filter({ hasText: targetExecution })
.first();
const getDragHandle = (row: Locator) =>
row.getByRole("button", { name: /draggable row/i }).first();
const sourceHandle = getDragHandle(sourceRow);
const targetHandle = getDragHandle(targetRow);

await expect(sourceRow).toBeVisible({ timeout: 5_000 });
await expect(targetRow).toBeVisible({ timeout: 5_000 });
await expect(sourceHandle).toBeVisible({ timeout: 5_000 });
await expect(targetHandle).toBeVisible({ timeout: 5_000 });
await sourceHandle.scrollIntoViewIfNeeded();
await targetHandle.scrollIntoViewIfNeeded();

const hasMoved = async () => {
const rows = await treeGrid.getByRole("row").allInnerTexts();
const sourceIndex = rows.findIndex((row) => row.includes(sourceExecution));
const targetIndex = rows.findIndex((row) => row.includes(targetExecution));
return (
sourceIndex !== -1 && targetIndex !== -1 && sourceIndex < targetIndex
);
};

const waitForMove = async () => {
try {
await expect.poll(hasMoved, { timeout: 4_000 }).toBe(true);
return true;
} catch {
return false;
}
};

let moved = false;

// Legacy pointer drag path works more reliably with some CI/browser combos.
const sourceText = sourceRow.getByText(sourceExecution).first();
const targetText = targetRow.getByText(targetExecution).first();
const sourceTextBox = await sourceText.boundingBox();
const targetTextBox = await targetText.boundingBox();
if (sourceTextBox && targetTextBox) {
await page.mouse.move(
sourceTextBox.x + sourceTextBox.width / 2,
sourceTextBox.y + sourceTextBox.height / 2,
);
await page.mouse.down();
await page.mouse.move(
targetTextBox.x + targetTextBox.width / 2,
targetTextBox.y + targetTextBox.height / 2,
{ steps: 20 },
);
await page.mouse.up();
moved = await waitForMove();
}

try {
if (!moved) {
await sourceHandle.dragTo(targetHandle, { timeout: 3_000 });
moved = await waitForMove();
}
} catch {
// Fall back to pointer/keyboard paths when dnd-kit does not trigger drag events in CI.
}

if (!moved) {
const sourceBox =
(await sourceHandle.boundingBox()) ?? (await sourceRow.boundingBox());
const targetBox =
(await targetHandle.boundingBox()) ?? (await targetRow.boundingBox());

if (sourceBox && targetBox) {
await page.mouse.move(
sourceBox.x + sourceBox.width / 2,
sourceBox.y + sourceBox.height / 2,
);
await page.mouse.down();
await page.mouse.move(
targetBox.x + targetBox.width / 2,
targetBox.y + Math.min(8, Math.max(2, targetBox.height / 4)),
{ steps: 20 },
);
await page.mouse.up();
moved = await waitForMove();
}
}

if (!moved) {
try {
await sourceRow.dragTo(targetRow, { timeout: 3_000 });
moved = await waitForMove();
} catch {
// Keep trying keyboard fallback below.
}
}

if (!moved) {
try {
await sourceHandle.focus({ timeout: 2_000 });
await page.keyboard.press("Space");
const rows = await treeGrid.getByRole("row").allInnerTexts();
const sourceIndex = rows.findIndex((row) =>
row.includes(sourceExecution),
);
const targetIndex = rows.findIndex((row) =>
row.includes(targetExecution),
);
const steps =
sourceIndex !== -1 && targetIndex !== -1 && sourceIndex > targetIndex
? sourceIndex - targetIndex
: 1;
for (let step = 0; step < steps; step++) {
await page.keyboard.press("ArrowUp");
}
await page.keyboard.press("Space");
moved = await waitForMove();
} catch {
// Keep "moved" false and let the caller assert/fail with context.
}
}

return moved;
}
22 changes: 6 additions & 16 deletions js/apps/admin-ui/test/autentication/flows.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
clickDefaultSwitchPolicy,
clickDeleteRow,
clickSwitchPolicy,
dragExecutionAboveExecution,
fillBindFlowModal,
fillCreateForm,
fillDuplicateFlowModal,
Expand Down Expand Up @@ -187,29 +188,18 @@ test.describe("Authentication flow details", () => {
});

test("drags and drops execution", async ({ page }) => {
test.setTimeout(60_000);
await using testBed = await createTestBed();

await adminClient.copyFlow("browser", flowName, testBed.realm);
await login(page, { to: toAuthentication({ realm: testBed.realm }) });

await clickTableRowItem(page, flowName);

const sourceBox = await page
.getByText("Identity Provider Redirector")
.boundingBox();
const targetBox = await page.getByText("Kerberos").boundingBox();

await page.mouse.move(
sourceBox!.x + sourceBox!.width / 2,
sourceBox!.y + sourceBox!.height / 2,
);
await page.mouse.down();
await page.mouse.move(
targetBox!.x + targetBox!.width / 2,
targetBox!.y + targetBox!.height / 2,
{ steps: 10 },
await dragExecutionAboveExecution(
page,
"Identity Provider Redirector",
"Kerberos",
);
await page.mouse.up();

await assertNotificationMessage(page, "Flow successfully updated");
});
Expand Down
Loading
Loading