Skip to content

Commit f871573

Browse files
authored
feat(clerk-js): Update OTP to use a single transparent field (#6551)
1 parent 05b6d65 commit f871573

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+438
-832
lines changed

.changeset/chilly-cities-write.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
---
4+
5+
Rework the OTP input to use a single transparent input (via `input-otp`) to improve password manager compatibility and iOS/Android SMS-based autofill. Removes individual digit fields; a single invisible input drives the six visual slots.
6+
7+
If you're using `@clerk/testing`, please ensure that you're using the latest version.

.changeset/rare-books-jam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/testing': minor
3+
---
4+
5+
Update `enterOtpCode` to support new OTP input. Please ensure you're testing against the latest versions of each @clerk package.

integration/playwright.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ export const common: PlaywrightTestConfig = {
1313
fullyParallel: true,
1414
forbidOnly: !!process.env.CI,
1515
retries: process.env.CI ? 5 : 0,
16-
timeout: 90_000,
1716
maxFailures: process.env.CI ? 5 : undefined,
1817
workers: process.env.CI ? '50%' : '70%',
1918
use: {
19+
actionTimeout: 10_000,
20+
navigationTimeout: 30_000,
2021
ignoreHTTPSErrors: true,
2122
trace: 'retain-on-failure',
2223
bypassCSP: true, // We probably need to limit this to specific tests

integration/tests/components.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,10 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('component
103103
await u.page.goToRelative(component.path, { waitUntil: 'commit' });
104104
await expect(u.page.getByText(component.fallback)).toBeVisible();
105105

106-
await signOut({ app, page, context });
106+
// eslint-disable-next-line playwright/no-conditional-in-test
107+
if (component.protected) {
108+
await signOut({ app, page, context });
109+
}
107110
});
108111
}
109112
});

integration/tests/dynamic-keys.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ test.describe('dynamic keys @nextjs', () => {
99
let app: Application;
1010

1111
test.beforeAll(async () => {
12+
test.setTimeout(90_000); // Wait for app to be ready
1213
app = await appConfigs.next.appRouter
1314
.clone()
1415
.addFile(

integration/tests/elements/next-sign-in.test.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,8 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S
7575

7676
await u.page.getByRole('button', { name: /use another method/i }).click();
7777
await u.po.signIn.getAltMethodsEmailCodeButton().click();
78-
await u.po.signIn.fillTestOtpCode('Enter email verification code');
79-
await page.waitForTimeout(2000);
80-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
78+
await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
79+
await page.keyboard.type('424242', { delay: 100 });
8180
await u.po.signIn.continue();
8281

8382
await u.page.waitForAppUrl('/');
@@ -110,8 +109,8 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S
110109
await u.page.getByRole('button', { name: /^use phone/i }).click();
111110
await u.po.signIn.getIdentifierInput().fill(fakeUserWithoutPassword.phoneNumber);
112111
await u.po.signIn.continue();
113-
await u.po.signIn.fillTestOtpCode('Enter phone verification code');
114-
await page.waitForTimeout(2000);
112+
await page.getByRole('textbox', { name: 'Enter phone verification code' }).click();
113+
await page.keyboard.type('424242', { delay: 100 });
115114
await u.po.signIn.continue();
116115

117116
await u.po.expect.toBeSignedIn();
@@ -146,9 +145,8 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S
146145
await u.po.signIn.continue();
147146
await u.page.getByRole('button', { name: /^forgot password/i }).click();
148147
await u.po.signIn.getResetPassword().click();
149-
await u.po.signIn.fillTestOtpCode('Enter email verification code');
150-
await page.waitForTimeout(2000);
151-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
148+
await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
149+
await page.keyboard.type('424242', { delay: 100 });
152150
await u.po.signIn.continue();
153151

154152
await u.po.signIn.setPassword(`${fakeUserWithPasword.password}_reset`);
@@ -187,9 +185,8 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S
187185

188186
await u.page.getByRole('button', { name: /use another method/i }).click();
189187
await u.po.signIn.getAltMethodsEmailCodeButton().click();
190-
await u.po.signIn.fillTestOtpCode('Enter email verification code');
191-
await page.waitForTimeout(2000);
192-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
188+
await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
189+
await page.keyboard.type('424242', { delay: 100 });
193190
await u.po.signIn.continue();
194191

195192
await u.po.expect.toBeSignedIn();

integration/tests/elements/next-sign-up.test.ts

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S
2525
password: fakeUser.password,
2626
});
2727

28-
await u.po.signUp.fillTestOtpCode('Enter email verification code');
29-
await page.waitForTimeout(2000);
30-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
28+
await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
29+
await page.keyboard.type('424242', { delay: 100 });
3130
await u.po.signUp.continue();
3231

3332
await u.page.waitForAppUrl('/');
@@ -54,9 +53,8 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S
5453
password: fakeUser.password,
5554
});
5655

57-
await u.po.signUp.fillTestOtpCode('Enter email verification code');
58-
await page.waitForTimeout(2000);
59-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
56+
await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
57+
await page.keyboard.type('424242', { delay: 100 });
6058
await u.po.signUp.continue();
6159

6260
await u.page.waitForAppUrl('/');
@@ -104,14 +102,12 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S
104102
password: fakeUser.password,
105103
});
106104

107-
await u.po.signUp.fillTestOtpCode('Enter phone verification code');
108-
await page.waitForTimeout(2000);
109-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
105+
await page.getByRole('textbox', { name: 'Enter phone verification code' }).click();
106+
await page.keyboard.type('424242', { delay: 100 });
110107
await u.po.signUp.continue();
111-
await page.waitForTimeout(2000);
112-
await u.po.signUp.fillTestOtpCode('Enter email verification code');
113-
await page.waitForTimeout(2000);
114-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
108+
109+
await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
110+
await page.keyboard.type('424242', { delay: 100 });
115111
await u.po.signUp.continue();
116112

117113
await u.po.expect.toBeSignedIn();
@@ -135,13 +131,12 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S
135131
password: fakeUser.password,
136132
});
137133

138-
await u.po.signUp.fillTestOtpCode('Enter phone verification code');
139-
await page.waitForTimeout(2000);
140-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
134+
await page.getByRole('textbox', { name: 'Enter phone verification code' }).click();
135+
await page.keyboard.type('424242', { delay: 100 });
141136
await u.po.signUp.continue();
142-
await u.po.signUp.fillTestOtpCode('Enter email verification code');
143-
await page.waitForTimeout(2000);
144-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
137+
138+
await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
139+
await page.keyboard.type('424242', { delay: 100 });
145140
await u.po.signUp.continue();
146141

147142
await u.po.expect.toBeSignedIn();
@@ -166,13 +161,12 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S
166161
password: fakeUser.password,
167162
});
168163

169-
await u.po.signUp.fillTestOtpCode('Enter phone verification code');
170-
await page.waitForTimeout(2000);
171-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
164+
await page.getByRole('textbox', { name: 'Enter phone verification code' }).click();
165+
await page.keyboard.type('424242', { delay: 100 });
172166
await u.po.signUp.continue();
173-
await u.po.signUp.fillTestOtpCode('Enter email verification code');
174-
await page.waitForTimeout(2000);
175-
// TODO: In original test the input has autoSubmit and this step is not needed. Not used right now because it didn't work.
167+
168+
await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
169+
await page.keyboard.type('424242', { delay: 100 });
176170
await u.po.signUp.continue();
177171

178172
await u.po.expect.toBeSignedIn();

integration/tests/global.setup.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { appConfigs } from '../presets';
55
import { fs, parseEnvOptions, startClerkJsHttpServer } from '../scripts';
66

77
setup('start long running apps', async () => {
8+
setup.setTimeout(90_000);
9+
810
await fs.ensureDir(constants.TMP_DIR);
911

1012
await startClerkJsHttpServer();

integration/tests/global.teardown.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { appConfigs } from '../presets';
66
import { killClerkJsHttpServer, parseEnvOptions } from '../scripts';
77

88
setup('teardown long running apps', async () => {
9+
setup.setTimeout(90_000);
10+
911
const { appUrl } = parseEnvOptions();
1012
await killClerkJsHttpServer();
1113

integration/tests/handshake.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ test.describe('Client handshake @generic', () => {
2727
const devBrowserCookie = '__clerk_db_jwt=needstobeset;';
2828

2929
test.beforeAll('setup local Clerk API mock', async () => {
30+
test.setTimeout(90_000); // Wait for app to be ready
3031
const env = appConfigs.envs.withEmailCodes
3132
.clone()
3233
.setEnvVariable('private', 'CLERK_API_URL', `http://localhost:${PORT}`);
@@ -984,6 +985,7 @@ test.describe('Client handshake with organization activation @nextjs', () => {
984985
let app: Application;
985986

986987
test.beforeAll('setup local jwks server', async () => {
988+
test.setTimeout(90_000); // Wait for app to be ready
987989
// Start the jwks server
988990
await new Promise<void>(resolve => jwksServer.listen(0, resolve));
989991
const address = jwksServer.address();
@@ -1367,6 +1369,7 @@ test.describe('Client handshake with an organization activation avoids infinite
13671369
let thisApp: Application;
13681370

13691371
test.beforeAll('setup local jwks server', async () => {
1372+
test.setTimeout(90_000); // Wait for app to be ready
13701373
// Start the jwks server
13711374
await new Promise<void>(resolve => jwksServer.listen(0, resolve));
13721375
const address = jwksServer.address();

0 commit comments

Comments
 (0)