Skip to content

Commit fe40688

Browse files
committed
fix assertion
1 parent 1ec34ff commit fe40688

2 files changed

Lines changed: 111 additions & 16 deletions

File tree

tests/integration/assertions/browser.test.ts

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,17 +266,93 @@ describe("browser assertion helpers", () => {
266266
}
267267
} as unknown as Page;
268268

269-
const selector = await __browserTestHooks.waitForPasswordInputAfterUsernameSubmit(
269+
const result = await __browserTestHooks.waitForPasswordInputAfterUsernameSubmit(
270270
page,
271271
'[data-testid="username-text-input"]',
272272
"agent@example.test",
273273
"app.example.test"
274274
);
275275

276-
expect(selector).toBe('[data-testid="password-text-input"]');
276+
expect(result).toEqual({ state: "password", selector: '[data-testid="password-text-input"]' });
277277
expect(currentUrl).toContain("/ui/v2/login/password");
278278
expect(passwordVisible).toBe(true);
279279
expect(submits).toBe(1);
280280
expect(evaluatedAt).toEqual(["http://issuer.example.test/ui/v2/login/loginname?requestId=oidc_123"]);
281281
});
282+
283+
test("does not treat the target app password field as a ZITADEL password step", async () => {
284+
const page = {
285+
url: () => "https://app.example.test/",
286+
locator: (selector: string) => ({
287+
first() {
288+
return this;
289+
},
290+
isVisible: async () => selector === 'input[type="password"]',
291+
innerText: async () => "Ubuntu 24.04.3 LTS\nUser name\nPassword\nLog in"
292+
}),
293+
waitForTimeout: async () => undefined
294+
} as unknown as Page;
295+
296+
const result = await __browserTestHooks.waitForPasswordInputAfterUsernameSubmit(
297+
page,
298+
'[data-testid="username-text-input"]',
299+
"agent@example.test",
300+
"app.example.test"
301+
);
302+
303+
expect(result).toEqual({ state: "target" });
304+
});
305+
306+
test("does not click a stale ZITADEL submit button after password Enter reaches the target", async () => {
307+
let currentUrl = "https://issuer.example.test/ui/v2/login/password?requestId=oidc_123";
308+
let clickedStaleSubmit = false;
309+
const passwordInput = {
310+
first() {
311+
return this;
312+
},
313+
focus: async () => undefined,
314+
press: async () => {
315+
currentUrl = "https://app.example.test/";
316+
},
317+
isVisible: async () => currentUrl.includes("/ui/v2/login/password"),
318+
isDisabled: async () => false
319+
};
320+
const submitButton = {
321+
first() {
322+
return this;
323+
},
324+
isVisible: async () => false,
325+
isDisabled: async () => false,
326+
click: async () => {
327+
clickedStaleSubmit = true;
328+
}
329+
};
330+
const page = {
331+
url: () => currentUrl,
332+
locator: (selector: string) => {
333+
if (selector === '[data-testid="password-text-input"]') {
334+
return passwordInput;
335+
}
336+
if (selector === '[data-testid="submit-button"]') {
337+
return submitButton;
338+
}
339+
return {
340+
first() {
341+
return this;
342+
},
343+
isVisible: async () => false,
344+
isDisabled: async () => false,
345+
click: async () => {
346+
clickedStaleSubmit = true;
347+
}
348+
};
349+
},
350+
waitForTimeout: async () => undefined
351+
} as unknown as Page;
352+
353+
await __browserTestHooks.submitIdentityForm(page, '[data-testid="password-text-input"]', ['[data-testid="submit-button"]']);
354+
355+
expect(currentUrl).toBe("https://app.example.test/");
356+
expect(clickedStaleSubmit).toBe(false);
357+
});
282358
});

tests/integration/assertions/browser.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { OidcTestUser } from "../types";
55

66
type LoginExpectation = "allow" | "deny";
77
type BrowserArtifactKind = "success" | "failure";
8+
type PasswordWaitResult = { state: "password"; selector: string } | { state: "target" };
89

910
type LoginOptions = {
1011
outputDir: string;
@@ -84,27 +85,33 @@ const CONSENT_SUBMIT_SELECTORS = ["Allow", "Authorize", "Approve", "Accept", "Co
8485
`input[type="button"][value="${label}"]`
8586
]);
8687

87-
async function waitForPasswordInputAfterUsernameSubmit(page: Page, usernameSelector: string, userEmail: string, targetHost: string): Promise<string> {
88+
async function waitForPasswordInputAfterUsernameSubmit(page: Page, usernameSelector: string, userEmail: string, targetHost: string): Promise<PasswordWaitResult> {
8889
const deadline = Date.now() + BROWSER_WAIT_TIMEOUT_MS;
8990
let lastResubmit = 0;
9091
let lastBody = "";
9192

9293
while (Date.now() < deadline) {
93-
if (isPasswordLoginStep(page.url())) {
94+
const currentUrl = page.url();
95+
if (isTargetApplicationPage(currentUrl, targetHost)) {
96+
return { state: "target" };
97+
}
98+
99+
if (isPasswordLoginStep(currentUrl)) {
94100
const selector = await visiblePasswordInputSelector(page, 5000);
95101
if (selector) {
96-
return selector;
102+
return { state: "password", selector };
97103
}
98104
}
99105

100-
for (const selector of PASSWORD_INPUT_SELECTORS) {
101-
const locator = page.locator(selector).first();
102-
if (await locatorVisible(locator)) {
103-
return selector;
106+
if (isIdentityLoginInputPage(currentUrl, targetHost)) {
107+
for (const selector of PASSWORD_INPUT_SELECTORS) {
108+
const locator = page.locator(selector).first();
109+
if (await locatorVisible(locator)) {
110+
return { state: "password", selector };
111+
}
104112
}
105113
}
106114

107-
const currentUrl = page.url();
108115
lastBody = (await maybeWithTimeout(page.locator("body").innerText({ timeout: 1000 }).catch(() => ""), 2000)) ?? "";
109116
const normalizedBody = lastBody.toLowerCase();
110117
if (
@@ -221,6 +228,7 @@ async function resubmitUsernameIfStillOnUsernameStep(page: Page, userEmail: stri
221228

222229
export const __browserTestHooks = {
223230
resubmitUsernameIfStillOnUsernameStep,
231+
submitIdentityForm,
224232
waitForPasswordInputAfterUsernameSubmit
225233
};
226234

@@ -270,8 +278,13 @@ async function clickFirstVisible(page: Page, selectors: string[]): Promise<boole
270278
(await locatorVisible(locator)) &&
271279
!(await locatorDisabled(locator))
272280
) {
273-
await locator.click({ noWaitAfter: true, timeout: BROWSER_CLICK_TIMEOUT_MS });
274-
return true;
281+
const clicked = await maybeWithTimeout(
282+
locator.click({ noWaitAfter: true, timeout: BROWSER_CLICK_TIMEOUT_MS }).then(() => true).catch(() => false),
283+
BROWSER_CLICK_TIMEOUT_MS + 1000
284+
);
285+
if (clicked) {
286+
return true;
287+
}
275288
}
276289
}
277290
return false;
@@ -649,7 +662,7 @@ async function submitIdentityForm(page: Page, inputSelector: string, buttonSelec
649662
}
650663

651664
if (await waitForEnabledWithin(page, buttonSelectors, 1000)) {
652-
await submitForm(page, buttonSelectors);
665+
await clickFirstVisible(page, buttonSelectors).catch(() => false);
653666
await page.waitForTimeout(750);
654667
if (await identitySubmissionAdvanced(page, beforeUrl, inputSelector)) {
655668
return;
@@ -1187,11 +1200,17 @@ async function loginThroughZitadelWithContext(
11871200
await submitIdentityForm(page, emailSelector, USERNAME_SUBMIT_SELECTORS);
11881201

11891202
stage = "waiting for password input after username submit";
1190-
const passwordSelector = await waitForPasswordInputAfterUsernameSubmit(page, emailSelector, user.email, targetHost);
1203+
const passwordWait = await waitForPasswordInputAfterUsernameSubmit(page, emailSelector, user.email, targetHost);
1204+
if (passwordWait.state === "target") {
1205+
stage = `waiting for ${expected} return to target host`;
1206+
await waitForReturnToTargetHost(page, targetHost, user.email, expected);
1207+
stage = "capturing success page";
1208+
return await finishBrowserLogin(page, screenshotPath, options);
1209+
}
11911210
stage = "entering password";
1192-
await typeInto(page, passwordSelector, user.password);
1211+
await typeInto(page, passwordWait.selector, user.password);
11931212
stage = "submitting password";
1194-
await submitIdentityForm(page, passwordSelector, PASSWORD_SUBMIT_SELECTORS);
1213+
await submitIdentityForm(page, passwordWait.selector, PASSWORD_SUBMIT_SELECTORS);
11951214

11961215
stage = `waiting for ${expected} return to target host`;
11971216
await waitForReturnToTargetHost(page, targetHost, user.email, expected);

0 commit comments

Comments
 (0)