Skip to content

Commit dd6c68b

Browse files
authored
fix: use click+type instead of fill for Angular login form compatibility (#202)
page.fill() sets values programmatically but Angular reactive forms require real keystroke events (keydown/keyup) to update their internal model. After fill+submit, the form appeared submitted but credentials were not recognized, leaving the browser on /login. Switch to click() + fill('') + type(value, {delay: 20}) pattern which generates real key events that Angular processes correctly.
1 parent 9fb6baa commit dd6c68b

File tree

2 files changed

+18
-7
lines changed

2 files changed

+18
-7
lines changed

packages/core/src/__tests__/auth/loginSelectors.test.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ vi.mock('../../errors.js', async () => {
77
return actual;
88
});
99

10-
type MockPage = Pick<Page, '$' | 'fill' | 'click'>;
10+
type MockPage = Pick<Page, '$' | 'fill' | 'click' | 'type'>;
1111

1212
function createMockPage(): MockPage {
1313
return {
1414
$: vi.fn(),
1515
fill: vi.fn(),
1616
click: vi.fn(),
17+
type: vi.fn(),
1718
} as unknown as MockPage;
1819
}
1920

@@ -115,15 +116,20 @@ describe('loginSelectors', () => {
115116
vi.mocked(page.$).mockResolvedValue(handle);
116117
vi.mocked(page.fill).mockResolvedValue(undefined);
117118
vi.mocked(page.click).mockResolvedValue(undefined);
119+
vi.mocked(page.type).mockResolvedValue(undefined);
118120

119121
const result = await loginSelectorsModule.tryAutoFill(page as Page, {
120122
email: 'test@example.com',
121123
password: 'secret',
122124
});
123125

124126
expect(result).toBe(true);
125-
expect(page.fill).toHaveBeenCalledWith('[data-e2e-id="loginForm"] input[name="username"]', 'test@example.com');
126-
expect(page.fill).toHaveBeenCalledWith('[data-e2e-id="loginForm"] input[name="password"]', 'secret');
127+
// Each field: click(selector) + fill(selector, '') + type(selector, value)
128+
expect(page.click).toHaveBeenCalledWith('[data-e2e-id="loginForm"] input[name="username"]');
129+
expect(page.type).toHaveBeenCalledWith('[data-e2e-id="loginForm"] input[name="username"]', 'test@example.com', { delay: 20 });
130+
expect(page.click).toHaveBeenCalledWith('[data-e2e-id="loginForm"] input[name="password"]');
131+
expect(page.type).toHaveBeenCalledWith('[data-e2e-id="loginForm"] input[name="password"]', 'secret', { delay: 20 });
132+
// Submit button click
127133
expect(page.click).toHaveBeenCalledWith('[data-e2e-id="loginScreenLoginButton"]');
128134
});
129135

@@ -132,15 +138,14 @@ describe('loginSelectors', () => {
132138
const handle = { description: 'element handle' } as unknown as ElementHandle;
133139
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
134140
vi.mocked(page.$).mockResolvedValue(handle);
135-
vi.mocked(page.fill).mockRejectedValue(new Error('fill failed'));
141+
vi.mocked(page.click).mockRejectedValue(new Error('click failed'));
136142

137143
const result = await loginSelectorsModule.tryAutoFill(page as Page, {
138144
email: 'test@example.com',
139145
});
140146

141147
expect(result).toBe(false);
142-
expect(page.fill).toHaveBeenCalledTimes(4);
143-
expect(page.click).not.toHaveBeenCalled();
148+
expect(page.click).toHaveBeenCalledTimes(4);
144149
expect(console.warn).toHaveBeenCalled();
145150
warnSpy.mockRestore();
146151
});

packages/core/src/auth/loginSelectors.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,13 @@ async function tryFillByCandidates(
160160
): Promise<boolean> {
161161
for (const candidate of candidates) {
162162
try {
163-
await page.fill(candidate.selector, value);
163+
// Use click + clear + type instead of fill — Angular reactive forms
164+
// need real keystroke events to update their internal model.
165+
// page.fill() dispatches input/change but skips keydown/keyup
166+
// which some SPA frameworks rely on for validation.
167+
await page.click(candidate.selector);
168+
await page.fill(candidate.selector, '');
169+
await page.type(candidate.selector, value, { delay: 20 });
164170
return true;
165171
} catch (error) {
166172
const normalized = normalizeError(error, 'Field fill failed', candidate.selector);

0 commit comments

Comments
 (0)