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
80 changes: 80 additions & 0 deletions js/apps/admin-ui/test/autentication/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,83 @@
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 sourceHandle = sourceRow.getByRole("button", {
name: "Drag handle",
exact: true,
});
const targetHandle = targetRow.getByRole("button", {
name: "Drag handle",
exact: true,
});

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;
}
};

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

let moved = await waitForMove();

if (!moved) {
const sourceBox = await sourceRow.boundingBox();
const targetBox = 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 + targetBox.height / 2,
{ steps: 20 },
);
await page.mouse.up();
moved = await waitForMove();
}
}

if (!moved) {
await sourceHandle.focus();

Check failure on line 219 in js/apps/admin-ui/test/autentication/flow.ts

View workflow job for this annotation

GitHub Actions / Admin UI E2E (chromium)

[chromium] › test/autentication/flows.spec.ts:190:3 › Authentication flow details › drags and drops execution

2) [chromium] › test/autentication/flows.spec.ts:190:3 › Authentication flow details › drags and drops execution Error: locator.focus: Test timeout of 60000ms exceeded. Call log: - waiting for getByRole('treegrid', { name: 'Flows' }).getByRole('row').filter({ hasText: 'execution Identity Provider Redirector' }).first().getByRole('button', { name: 'Drag handle', exact: true }) at autentication/flow.ts:219 217 | 218 | if (!moved) { > 219 | await sourceHandle.focus(); | ^ 220 | await page.keyboard.press("Space"); 221 | await page.keyboard.press("ArrowUp"); 222 | await page.keyboard.press("Space"); at dragExecutionAboveExecution (/home/runner/work/keycloak/keycloak/js/apps/admin-ui/test/autentication/flow.ts:219:24) at /home/runner/work/keycloak/keycloak/js/apps/admin-ui/test/autentication/flows.spec.ts:198:19
await page.keyboard.press("Space");
await page.keyboard.press("ArrowUp");
await page.keyboard.press("Space");
moved = await waitForMove();
}

return moved;
}
23 changes: 7 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,30 +188,20 @@ 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 },
const moved = await dragExecutionAboveExecution(
page,
"execution Identity Provider Redirector",
"execution Kerberos",
);
await page.mouse.up();

expect(moved).toBe(true);
await assertNotificationMessage(page, "Flow successfully updated");
});

Expand Down
127 changes: 69 additions & 58 deletions js/apps/admin-ui/test/client-scope/oid4vci-client-scope.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@ import { clickTableRowItem, clickTableToolbarItem } from "../utils/table.ts";
import { login } from "../utils/login.ts";
import { toClientScopes } from "../../src/client-scopes/routes/ClientScopes.tsx";

type Oid4vciFormat = "SD-JWT VC (dc+sd-jwt)" | "JWT VC (jwt_vc_json)";
const OID4VCI_PROTOCOL = "OpenID for Verifiable Credentials";
const OID4VCI_SERVER_FEATURE = "OID4VC_VCI";
const OID4VCI_OPTION_VISIBLE_TIMEOUT_MS = 5_000;
const OID4VCI_UNAVAILABLE_MESSAGE =
"OID4VCI protocol is unavailable. Start Keycloak with verifiable credentials support enabled.";

async function skipIfOID4VCIFeatureDisabled() {
const isOID4VCIFeatureEnabled = await adminClient.isFeatureEnabled(
OID4VCI_SERVER_FEATURE,
);
// eslint-disable-next-line playwright/no-skipped-test -- Explicit environment gate for unsupported server features.
test.skip(!isOID4VCIFeatureEnabled, OID4VCI_UNAVAILABLE_MESSAGE);
}

async function getVisibleOID4VCIProtocolOption(page: Page) {
const oid4vcOption = page.getByRole("option", { name: OID4VCI_PROTOCOL });
await oid4vcOption.waitFor({
state: "visible",
timeout: OID4VCI_OPTION_VISIBLE_TIMEOUT_MS,
});
return oid4vcOption;
}

// Helper function to create client scope (without selecting protocol)
async function createClientScope(
page: Page,
Expand All @@ -21,27 +45,27 @@ async function createClientScope(
await login(page, { to: toClientScopes({ realm: testBed.realm }) });

await goToClientScopes(page);
await page.waitForLoadState("domcontentloaded");
await expect(page.getByPlaceholder("Search for client scope")).toBeVisible();

await clickTableToolbarItem(page, "Create client scope");
await page.waitForLoadState("domcontentloaded");
await expect(
page.getByRole("heading", { name: "Create client scope" }),
).toBeVisible();
await expect(page.getByTestId("name")).toBeVisible();
}

// Helper function to create client scope and select protocol/format
async function createClientScopeAndSelectProtocolAndFormat(
page: Page,
testBed: Awaited<ReturnType<typeof createTestBed>>,
format?: "SD-JWT VC (dc+sd-jwt)" | "JWT VC (jwt_vc_json)",
format?: Oid4vciFormat,
) {
await createClientScope(page, testBed);

await selectItem(page, "#kc-protocol", "OpenID for Verifiable Credentials");

await page.waitForLoadState("domcontentloaded");
await selectOID4VCIProtocol(page);

if (format) {
await selectItem(page, "#kc-vc-format", format);
await page.waitForLoadState("domcontentloaded");
await selectVCFormat(page, format);
}
}

Expand All @@ -56,12 +80,13 @@ async function navigateBackAndVerifyClientScope(
await page.goto(
`${baseUrl}#${toClientScopes({ realm: testBed.realm }).pathname!}`,
);
await page.waitForLoadState("domcontentloaded");
await expect(page.getByPlaceholder("Search for client scope")).toBeVisible();

await page.getByPlaceholder("Search for client scope").fill(clientScopeName);
await page.keyboard.press("Enter");

await clickTableRowItem(page, clientScopeName);
await page.waitForLoadState("domcontentloaded");
await expect(page.getByTestId("name")).toHaveValue(clientScopeName);
}

// OID4VCI field selectors
Expand Down Expand Up @@ -108,6 +133,22 @@ const TEST_VALUES = {
const TOKEN_JWS_TYPE_WARNING_PREFIX =
"The configured Token JWS Type does not match the recommended value for the selected credential format.";

async function selectOID4VCIProtocol(page: Page) {
await skipIfOID4VCIFeatureDisabled();

await expect(page.locator("#kc-protocol")).toBeVisible();
await page.locator("#kc-protocol").click();

const oid4vcOption = await getVisibleOID4VCIProtocolOption(page);
await oid4vcOption.click();
await expect(page.locator("#kc-protocol")).toContainText(OID4VCI_PROTOCOL);
}

async function selectVCFormat(page: Page, format: Oid4vciFormat) {
await selectItem(page, "#kc-vc-format", format);
await expect(page.locator("#kc-vc-format")).toContainText(format);
}

// Helper function to fill TimeSelector fields (enters value in seconds - the base unit)
async function fillTimeSelectorValue(
page: Page,
Expand Down Expand Up @@ -149,22 +190,7 @@ test.describe("OID4VCI Client Scope Functionality", () => {
});
await createClientScope(page, testBed);

await expect(page.locator("#kc-protocol")).toBeVisible();

const protocolButton = page.locator("#kc-protocol");
await protocolButton.click();

const oid4vcOption = page.getByRole("option", {
name: "OpenID for Verifiable Credentials",
});
await expect(oid4vcOption).toBeVisible();
await oid4vcOption.click();

await page.waitForLoadState("domcontentloaded");

await expect(page.locator("#kc-protocol")).toContainText(
"OpenID for Verifiable Credentials",
);
await selectOID4VCIProtocol(page);

await expect(
page.getByTestId(OID4VCI_FIELDS.CREDENTIAL_CONFIGURATION_ID),
Expand Down Expand Up @@ -294,11 +320,10 @@ test.describe("OID4VCI Client Scope Functionality", () => {

await expect(page.locator("#kc-protocol")).toBeVisible();

await skipIfOID4VCIFeatureDisabled();
await page.locator("#kc-protocol").click();

await expect(
page.getByRole("option", { name: "OpenID for Verifiable Credentials" }),
).toBeVisible();
const oid4vcOption = await getVisibleOID4VCIProtocolOption(page);
await expect(oid4vcOption).toBeVisible();
});

test("should not display OID4VCI fields when protocol is not OID4VCI", async ({
Expand All @@ -318,8 +343,6 @@ test.describe("OID4VCI Client Scope Functionality", () => {
await expect(openidConnectOption).toBeVisible();
await openidConnectOption.click();

await page.waitForLoadState("domcontentloaded");

await expect(
page.getByTestId(OID4VCI_FIELDS.CREDENTIAL_CONFIGURATION_ID),
).toBeHidden();
Expand Down Expand Up @@ -350,22 +373,21 @@ test.describe("OID4VCI Client Scope Functionality", () => {
await protocolButton.click();

const oid4vcOption = page.getByRole("option", {
name: "OpenID for Verifiable Credentials",
name: OID4VCI_PROTOCOL,
});
const openidConnectOption = page.getByRole("option", {
name: "OpenID Connect",
});

await skipIfOID4VCIFeatureDisabled();
const oid4vcVisibleOption = await getVisibleOID4VCIProtocolOption(page);

await expect(oid4vcOption).toBeVisible();
await expect(openidConnectOption).toBeVisible();

await oid4vcOption.click();
await oid4vcVisibleOption.click();

await page.waitForLoadState("domcontentloaded");

await expect(page.locator("#kc-protocol")).toContainText(
"OpenID for Verifiable Credentials",
);
await expect(page.locator("#kc-protocol")).toContainText(OID4VCI_PROTOCOL);

await expect(
page.getByTestId(OID4VCI_FIELDS.CREDENTIAL_CONFIGURATION_ID),
Expand Down Expand Up @@ -552,35 +574,27 @@ test.describe("OID4VCI Client Scope Functionality", () => {
page.getByTestId(OID4VCI_FIELDS.VERIFIABLE_CREDENTIAL_TYPE),
).toBeVisible();

await selectItem(page, "#kc-vc-format", "JWT VC (jwt_vc_json)");

await page.waitForLoadState("domcontentloaded");
await selectVCFormat(page, "JWT VC (jwt_vc_json)");

await expect(
page.getByTestId(OID4VCI_FIELDS.VERIFIABLE_CREDENTIAL_TYPE),
).toBeHidden();

await selectItem(page, "#kc-vc-format", "SD-JWT VC (dc+sd-jwt)");

await page.waitForLoadState("domcontentloaded");
await selectVCFormat(page, "SD-JWT VC (dc+sd-jwt)");

await expect(
page.getByTestId(OID4VCI_FIELDS.VERIFIABLE_CREDENTIAL_TYPE),
).toBeVisible();
await expect(page.getByTestId(OID4VCI_FIELDS.VISIBLE_CLAIMS)).toBeVisible();

await selectItem(page, "#kc-vc-format", "JWT VC (jwt_vc_json)");

await page.waitForLoadState("domcontentloaded");
await selectVCFormat(page, "JWT VC (jwt_vc_json)");

await expect(
page.getByTestId(OID4VCI_FIELDS.VERIFIABLE_CREDENTIAL_TYPE),
).toBeHidden();
await expect(page.getByTestId(OID4VCI_FIELDS.VISIBLE_CLAIMS)).toBeHidden();

await selectItem(page, "#kc-vc-format", "SD-JWT VC (dc+sd-jwt)");

await page.waitForLoadState("domcontentloaded");
await selectVCFormat(page, "SD-JWT VC (dc+sd-jwt)");

await expect(
page.getByTestId(OID4VCI_FIELDS.VERIFIABLE_CREDENTIAL_TYPE),
Expand All @@ -600,8 +614,7 @@ test.describe("OID4VCI Client Scope Functionality", () => {

await expect(page.getByTestId(OID4VCI_FIELDS.TOKEN_JWS_TYPE)).toBeVisible();

await selectItem(page, "#kc-vc-format", "SD-JWT VC (dc+sd-jwt)");
await page.waitForLoadState("domcontentloaded");
await selectVCFormat(page, "SD-JWT VC (dc+sd-jwt)");

await expect(page.getByTestId(OID4VCI_FIELDS.TOKEN_JWS_TYPE)).toBeVisible();
});
Expand All @@ -627,8 +640,7 @@ test.describe("OID4VCI Client Scope Functionality", () => {
await tokenJwsType.fill("vc+jwt");
await expect(page.getByText(TOKEN_JWS_TYPE_WARNING_PREFIX)).toHaveCount(0);

await selectItem(page, "#kc-vc-format", "SD-JWT VC (dc+sd-jwt)");
await page.waitForLoadState("domcontentloaded");
await selectVCFormat(page, "SD-JWT VC (dc+sd-jwt)");

await tokenJwsType.fill("vc+jwt");
await expect(page.getByText(TOKEN_JWS_TYPE_WARNING_PREFIX)).toBeVisible();
Expand All @@ -654,8 +666,7 @@ test.describe("OID4VCI Client Scope Functionality", () => {
await tokenJwsType.fill("");
await expect(page.getByText(TOKEN_JWS_TYPE_WARNING_PREFIX)).toHaveCount(0);

await selectItem(page, "#kc-vc-format", "JWT VC (jwt_vc_json)");
await page.waitForLoadState("domcontentloaded");
await selectVCFormat(page, "JWT VC (jwt_vc_json)");

await tokenJwsType.fill("");
await expect(page.getByText(TOKEN_JWS_TYPE_WARNING_PREFIX)).toHaveCount(0);
Expand Down Expand Up @@ -1012,7 +1023,7 @@ test.describe("OID4VCI Client Scope Functionality", () => {

await expect(
page.getByRole("option", {
name: "OpenID for Verifiable Credentials",
name: OID4VCI_PROTOCOL,
}),
).toHaveCount(0);
} finally {
Expand Down
Loading
Loading