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
7 changes: 7 additions & 0 deletions .changeset/chilly-cities-write.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
---

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.

If you're using `@clerk/testing`, please ensure that you're using the latest version.
5 changes: 5 additions & 0 deletions .changeset/rare-books-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/testing': minor
---

Update `enterOtpCode` to support new OTP input. Please ensure you're testing against the latest versions of each @clerk package.
3 changes: 2 additions & 1 deletion integration/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ export const common: PlaywrightTestConfig = {
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 5 : 0,
timeout: 90_000,
maxFailures: process.env.CI ? 5 : undefined,
workers: process.env.CI ? '50%' : '70%',
use: {
actionTimeout: 10_000,
navigationTimeout: 30_000,
Copy link
Member Author

@tmilewski tmilewski Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relatively safe generic timeouts. More-specific ones are assigned (at the existing 90s) in various beforeAll calls and tests containing similar logic.

ignoreHTTPSErrors: true,
trace: 'retain-on-failure',
bypassCSP: true, // We probably need to limit this to specific tests
Expand Down
5 changes: 4 additions & 1 deletion integration/tests/components.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('component
await u.page.goToRelative(component.path, { waitUntil: 'commit' });
await expect(u.page.getByText(component.fallback)).toBeVisible();

await signOut({ app, page, context });
// eslint-disable-next-line playwright/no-conditional-in-test
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to run if it's never signed in, per a similar if statement.

if (component.protected) {
await signOut({ app, page, context });
}
});
}
});
1 change: 1 addition & 0 deletions integration/tests/dynamic-keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ test.describe('dynamic keys @nextjs', () => {
let app: Application;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouter
.clone()
.addFile(
Expand Down
19 changes: 8 additions & 11 deletions integration/tests/elements/next-sign-in.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These particular actions don't apply to our generic OTP component and, as such, are handled differently... and aren't common thus shouldn't go in @clerk/testing.

await u.po.signIn.continue();

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

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

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

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

await u.po.expect.toBeSignedIn();
Expand Down
44 changes: 19 additions & 25 deletions integration/tests/elements/next-sign-up.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js S
password: fakeUser.password,
});

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

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

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

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

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

await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
await page.keyboard.type('424242', { delay: 100 });
await u.po.signUp.continue();

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

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

await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
await page.keyboard.type('424242', { delay: 100 });
await u.po.signUp.continue();

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

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

await page.getByRole('textbox', { name: 'Enter email verification code' }).click();
await page.keyboard.type('424242', { delay: 100 });
await u.po.signUp.continue();

await u.po.expect.toBeSignedIn();
Expand Down
2 changes: 2 additions & 0 deletions integration/tests/global.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { appConfigs } from '../presets';
import { fs, parseEnvOptions, startClerkJsHttpServer } from '../scripts';

setup('start long running apps', async () => {
setup.setTimeout(90_000);

await fs.ensureDir(constants.TMP_DIR);

await startClerkJsHttpServer();
Expand Down
2 changes: 2 additions & 0 deletions integration/tests/global.teardown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { appConfigs } from '../presets';
import { killClerkJsHttpServer, parseEnvOptions } from '../scripts';

setup('teardown long running apps', async () => {
setup.setTimeout(90_000);

const { appUrl } = parseEnvOptions();
await killClerkJsHttpServer();

Expand Down
3 changes: 3 additions & 0 deletions integration/tests/handshake.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ test.describe('Client handshake @generic', () => {
const devBrowserCookie = '__clerk_db_jwt=needstobeset;';

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

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

test.beforeAll('setup local jwks server', async () => {
test.setTimeout(90_000); // Wait for app to be ready
// Start the jwks server
await new Promise<void>(resolve => jwksServer.listen(0, resolve));
const address = jwksServer.address();
Expand Down
2 changes: 1 addition & 1 deletion integration/tests/legal-consent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withLegalConsent] })(
});

// We don't need to fill in the OTP code, because the user is not signed up
await expect(page.getByRole('textbox', { name: /digit 1/i })).toBeHidden();
await expect(page.getByLabel('Enter verification code')).toBeHidden();

// Check if user is signed out
await u.po.expect.toBeSignedOut();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ test.describe('multiple apps running on localhost using different Clerk instance
let apps: Array<{ app: Application; serverUrl: string }>;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for apps to be ready
apps = await Promise.all([prepareApplication('sessions-dev-1'), prepareApplication('sessions-dev-2')]);

const u = apps.map(a => createTestUtils({ app: a.app }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ test.describe('multiple apps running on localhost using same Clerk instance @loc
let apps: Array<{ app: Application; serverUrl: string }>;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for apps to be ready
apps = await Promise.all([prepareApplication('sessions-dev-1'), prepareApplication('sessions-dev-1')]);

const u = apps.map(a => createTestUtils({ app: a.app }));
Expand Down
2 changes: 2 additions & 0 deletions integration/tests/localhost/localhost-switch-instance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ test.describe('switching instances on localhost same port @localhost', () => {
});

test('apps can be used without clearing the cookies after instance switch', async ({ context }) => {
test.setTimeout(90_000); // Wait for app to be ready

// We need both apps to run on the same port
const port = await getPort();
// Create app and user for the 1st app
Expand Down
3 changes: 3 additions & 0 deletions integration/tests/machine-auth/api-keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ test.describe('Next.js API key auth within clerkMiddleware() @machine', () => {
let fakeAPIKey: FakeAPIKey;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouter
.clone()
.addFile(
Expand Down Expand Up @@ -105,6 +106,8 @@ test.describe('Next.js API key auth within routes @nextjs', () => {
let fakeAPIKey: FakeAPIKey;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready

app = await appConfigs.next.appRouter
.clone()
.addFile(
Expand Down
1 change: 1 addition & 0 deletions integration/tests/machine-auth/m2m.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ test.describe('machine-to-machine auth @machine', () => {
let analyticsServerM2MToken: M2MToken;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
const fakeCompanyName = faker.company.name();

// Create primary machine using instance secret key
Expand Down
3 changes: 3 additions & 0 deletions integration/tests/middleware-placement.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ test.describe('next start - missing middleware @quickstart', () => {
let app: Application;

test.beforeAll(async () => {
test.setTimeout(90_000);
app = await commonSetup.commit();
await app.setup();
await app.withEnv(appConfigs.envs.withEmailCodesQuickstart);
Expand All @@ -44,6 +45,7 @@ test.describe('next start - invalid middleware at root on src/ @quickstart', ()
let app: Application;

test.beforeAll(async () => {
test.setTimeout(90_000);
app = await commonSetup.addFile('middleware.ts', () => middlewareFileContents).commit();
await app.setup();
await app.withEnv(appConfigs.envs.withEmailCodesQuickstart);
Expand Down Expand Up @@ -74,6 +76,7 @@ test.describe('next start - invalid middleware inside app on src/ @quickstart',
let app: Application;

test.beforeAll(async () => {
test.setTimeout(90_000);
app = await commonSetup.addFile('src/app/middleware.ts', () => middlewareFileContents).commit();
await app.setup();
await app.withEnv(appConfigs.envs.withEmailCodesQuickstart);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ test.describe('Next with ClerkJS V4 <-> Account Portal Core 1 @ap-flows', () =>
let fakeUser: FakeUser;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouterAPWithClerkNextV4.clone().commit();
await app.setup();
await app.withEnv(appConfigs.envs.withAPCore1ClerkV4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ test.describe('Next with ClerkJS V4 <-> Account Portal Core 2 @ap-flows', () =>
let fakeUser: FakeUser;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouterAPWithClerkNextV4.clone().commit();
await app.setup();
await app.withEnv(appConfigs.envs.withAPCore2ClerkV4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ test.describe('Next with ClerkJS V5 <-> Account Portal Core 1 @ap-flows', () =>
let fakeUser: FakeUser;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouterAPWithClerkNextLatest.clone().commit();
await app.setup();
await app.withEnv(appConfigs.envs.withAPCore1ClerkLatest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ test.describe('Next with ClerkJS V5 <-> Account Portal Core 2 @ap-flows', () =>
let fakeUser: FakeUser;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouterAPWithClerkNextLatest.clone().commit();
await app.setup();
await app.withEnv(appConfigs.envs.withAPCore2ClerkLatest);
Expand Down
3 changes: 2 additions & 1 deletion integration/tests/next-account-portal/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ export const testSignUp = async ({ app, page, context }: TestParams) => {

// Sign up with email and password
await u.po.signUp.signUpWithEmailAndPassword({ email: tempUser.email, password: tempUser.password });
await u.po.signUp.enterOtpCode('424242');

await u.po.signUp.enterTestOtpCode();

// Navigate back to localhost
await u.page.waitForAppUrl('/');
Expand Down
2 changes: 2 additions & 0 deletions integration/tests/next-build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ test.describe('next build - provider as client component @nextjs', () => {
let app: Application;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouter
.clone()
.addFile(
Expand Down Expand Up @@ -105,6 +106,7 @@ test.describe('next build - dynamic options @nextjs', () => {
let app: Application;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouter
.clone()
.addFile(
Expand Down
1 change: 1 addition & 0 deletions integration/tests/session-tasks-eject-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ test.describe('session tasks eject flow @nextjs', () => {
let user: FakeUser;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouter
.clone()
.addFile(
Expand Down
1 change: 1 addition & 0 deletions integration/tests/sign-in-or-up-component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ test.describe('sign-in-or-up component initialization flow @nextjs', () => {
let app: Application;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouter.clone().commit();
await app.setup();
await app.withEnv(appConfigs.envs.withEmailCodes);
Expand Down
1 change: 1 addition & 0 deletions integration/tests/sign-in-or-up-restricted-mode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ test.describe('sign-in-or-up restricted mode @nextjs', () => {
let fakeUser: FakeUser;

test.beforeAll(async () => {
test.setTimeout(90_000); // Wait for app to be ready
app = await appConfigs.next.appRouter.clone().commit();
await app.setup();
await app.withEnv(appConfigs.envs.withSignInOrUpwithRestrictedModeFlow);
Expand Down
4 changes: 2 additions & 2 deletions packages/clerk-js/bundlewatch.config.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"files": [
{ "path": "./dist/clerk.js", "maxSize": "629KB" },
{ "path": "./dist/clerk.js", "maxSize": "631KB" },
{ "path": "./dist/clerk.browser.js", "maxSize": "78KB" },
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "119KB" },
{ "path": "./dist/clerk.headless*.js", "maxSize": "61KB" },
{ "path": "./dist/ui-common*.js", "maxSize": "117.1KB" },
{ "path": "./dist/ui-common*.legacy.*.js", "maxSize": "118KB" },
{ "path": "./dist/vendors*.js", "maxSize": "41KB" },
{ "path": "./dist/vendors*.js", "maxSize": "43.78KB" },
{ "path": "./dist/coinbase*.js", "maxSize": "38KB" },
{ "path": "./dist/stripe-vendors*.js", "maxSize": "1KB" },
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },
Expand Down
Loading
Loading