diff --git a/__e2e__/authFlow.harness.js b/__e2e__/authFlow.harness.js
index 3f10762..3aae366 100644
--- a/__e2e__/authFlow.harness.js
+++ b/__e2e__/authFlow.harness.js
@@ -3,11 +3,15 @@ import { screen, userEvent } from '@react-native-harness/ui';
import React from 'react';
import PrewarmAuthApp, { authServer } from './PrewarmAuthApp';
+// `render` gives up after 1s by default, and the first render in a process creates the first
+// native WebView, which on the CI Android emulator takes longer than that.
+const MOUNT_OPTIONS = { timeout: 10000 };
+
describe('live SDK multi-screen auth flow', () => {
for (const passiveSiteKey of [false, true]) {
test(`signs up and logs in after destroying the warmed WebView (passiveSiteKey=${passiveSiteKey})`, async () => {
await fetch(`${authServer}/reset`, { method: 'POST' });
- await render();
+ await render(, MOUNT_OPTIONS);
await userEvent.type(await screen.findByTestId('email'), 'destroy@example.test');
for (const [attempt, mode] of ['signup', 'login'].entries()) {
await userEvent.press(await screen.findByTestId(mode));
@@ -30,7 +34,7 @@ describe('live SDK multi-screen auth flow', () => {
for (const passiveSiteKey of [false, true]) {
test(`prewarms, signs up, and logs in without reloading (passiveSiteKey=${passiveSiteKey})`, async () => {
await fetch(`${authServer}/reset`, { method: 'POST' });
- await render();
+ await render(, MOUNT_OPTIONS);
await waitFor(
async () => {
const state = await (await fetch(`${authServer}/state`)).json();
@@ -75,7 +79,7 @@ describe('live SDK multi-screen auth flow', () => {
test('cancels queued verification and reuses the warmed SDK on retry', async () => {
await fetch(`${authServer}/reset`, { method: 'POST' });
- await render();
+ await render(, MOUNT_OPTIONS);
await userEvent.type(await screen.findByTestId('email'), 'cancel@example.test');
await userEvent.press(await screen.findByTestId('signup'));
await userEvent.press(await screen.findByTestId('cancel'));
@@ -96,7 +100,7 @@ describe('live SDK multi-screen auth flow', () => {
test('fetches a fresh token after the backend rejects credentials', async () => {
await fetch(`${authServer}/reset`, { method: 'POST' });
const measurements = { attempts: [], errors: [], authErrors: [] };
- await render();
+ await render(, MOUNT_OPTIONS);
await userEvent.type(await screen.findByTestId('email'), 'retry@example.test');
for (const [mode, password, tokenCount] of [['signup', 'Password-123', 1], ['login', 'Password', 2]]) {
await userEvent.press(await screen.findByTestId(mode));
diff --git a/__e2e__/example.harness.js b/__e2e__/example.harness.js
index c6d48e2..4289f95 100644
--- a/__e2e__/example.harness.js
+++ b/__e2e__/example.harness.js
@@ -6,6 +6,10 @@ import App from '../App';
import AutopassExample from './ExampleAutopass';
import { authServer } from './PrewarmAuthApp';
+// `render` gives up after 1s by default, and the first render in a process creates the first
+// native WebView, which on the CI Android emulator takes longer than that.
+const MOUNT_OPTIONS = { timeout: 10000 };
+
async function saveScreenshot(phase) {
const screenshot = await screen.screenshot();
expect(screenshot).not.toBeNull();
@@ -35,7 +39,7 @@ describe('original repository example', () => {
afterEach(() => log.mockRestore());
test('opens, dismisses, and reopens the live demo challenge', async () => {
- await render();
+ await render(, MOUNT_OPTIONS);
for (const attempt of [1, 2]) {
await launchExample(log);
await waitFor(() => expect(log.mock.calls.filter(([message]) => message === 'Visual challenge opened')).toHaveLength(attempt), { timeout: 30000 });
@@ -56,7 +60,7 @@ describe('original repository example', () => {
// Generated from Example.App.js; only the sitekey changes to the public auto-pass key.
test('displays valid tokens and allows a second launch with the auto-pass key', async () => {
await fetch(`${authServer}/reset`, { method: 'POST' });
- await render();
+ await render(, MOUNT_OPTIONS);
for (const attempt of [1, 2]) {
await launchExample(log);
await waitFor(() => expect(log.mock.calls.filter(([message]) => message === 'Verified code from hCaptcha')).toHaveLength(attempt), { timeout: 30000 });
diff --git a/__e2e__/prewarm.harness.js b/__e2e__/prewarm.harness.js
index c45357a..acd12d0 100644
--- a/__e2e__/prewarm.harness.js
+++ b/__e2e__/prewarm.harness.js
@@ -39,6 +39,9 @@ const sdk = `
window.hCaptchaOnLoad();
`;
const jsSrc = `data:application/javascript,${encodeURIComponent(sdk)}#`;
+// `render` gives up after 1s by default, and the first render in a process creates the first
+// native WebView, which on the CI Android emulator takes longer than that.
+const MOUNT_OPTIONS = { timeout: 10000 };
// Native process startup is separate from the library's 15-second loading deadline.
const sdkReadyOptions = { timeout: 30000 };
const waitForSdk = (testId) => waitFor(() => expect(screen.queryByTestId(testId)).not.toBeNull(), sdkReadyOptions);
@@ -115,7 +118,7 @@ describe('native WebView prewarm', () => {
const lateSdk = `setTimeout(function() { ${sdk} }, 16000);`;
await render( events.push(event)} />);
+ onEvent={(event) => events.push(event)} />, MOUNT_OPTIONS);
await waitForSdk('loads-1');
expect(events.map((event) => event.nativeEvent.data)).toEqual(['fixture-ready']);
ref.current.execute();
@@ -130,7 +133,7 @@ describe('native WebView prewarm', () => {
const events = [];
await render( events.push(event)}
- sdkSource={`data:application/javascript,${encodeURIComponent('window.fixtureRenderFailures = 2;\n' + sdk)}#`} />);
+ sdkSource={`data:application/javascript,${encodeURIComponent('window.fixtureRenderFailures = 2;\n' + sdk)}#`} />, MOUNT_OPTIONS);
await waitFor(async () => {
const state = await (await fetch(`${authServer}/state`)).json();
expect(state.sdkReady).toBe(1);
@@ -151,7 +154,7 @@ describe('native WebView prewarm', () => {
const key = `hidden-reset-${variant}`;
const props = { captchaRef: ref, delayedTokens: true,
inline: variant === 'inline', passiveSiteKey: variant === 'passive', onEvent: (event) => events.push(event) };
- const fixture = await render();
+ const fixture = await render(, MOUNT_OPTIONS);
await waitForSdk('loads-1');
ref.current.execute();
await waitFor(() => expect(events.filter((event) => event.markUsed)).toHaveLength(1), { timeout: 10000 });
@@ -180,7 +183,7 @@ describe('native WebView prewarm', () => {
const props = { captchaRef: ref, inline: variant === 'inline', passiveSiteKey: variant === 'passive',
onEvent: (event) => events.push(event) };
const fixture = await render();
+ sdkSource={`${authServer}/slow/1/api.js`} />, MOUNT_OPTIONS);
await waitFor(async () => {
const state = await (await fetch(`${authServer}/state`)).json();
expect(state.sdkLoads).toBe(1);
@@ -203,7 +206,7 @@ describe('native WebView prewarm', () => {
const events = [];
await render( events.push(event)} />);
+ delayedTokens={phase === 'pending'} hideAfterToken={false} onEvent={(event) => events.push(event)} />, MOUNT_OPTIONS);
await waitForSdk('loads-1');
if (phase !== 'ready') {
ref.current.execute();
@@ -231,7 +234,7 @@ describe('native WebView prewarm', () => {
for (const passiveSiteKey of [false, true]) {
test(`loads while hidden and reuses the widget (passiveSiteKey=${passiveSiteKey})`, async () => {
- await render();
+ await render(, MOUNT_OPTIONS);
await waitForSdk('ready');
for (const attempt of [1, 2]) {
await userEvent.press(await screen.findByTestId('execute'));
@@ -241,7 +244,7 @@ describe('native WebView prewarm', () => {
});
test(`rejects a cancelled outcome after re-execute (passiveSiteKey=${passiveSiteKey})`, async () => {
- await render();
+ await render(, MOUNT_OPTIONS);
await waitForSdk('ready');
await userEvent.press(await screen.findByTestId('execute'));
await new Promise((resolve) => setTimeout(resolve, 2500));
@@ -251,7 +254,7 @@ describe('native WebView prewarm', () => {
});
test(`preserves a held token across presentation change (passiveSiteKey=${passiveSiteKey})`, async () => {
- const fixture = await render();
+ const fixture = await render(, MOUNT_OPTIONS);
await waitForSdk('ready');
await userEvent.press(await screen.findByTestId('execute'));
await waitFor(() => screen.findByTestId('fixture-token-widget-1-attempt-1-000000000000'));
@@ -263,7 +266,7 @@ describe('native WebView prewarm', () => {
}
test('recreates the native document after a theme change', async () => {
- const fixture = await render();
+ const fixture = await render(, MOUNT_OPTIONS);
await waitForSdk('loads-1');
await userEvent.press(await screen.findByTestId('execute'));
await waitFor(() => screen.findByTestId('fixture-token-widget-1-attempt-1-000000000000'));
@@ -278,7 +281,7 @@ describe('native WebView prewarm', () => {
test(`updates verification data with the expected reload behavior (legacy=${legacy})`, async () => {
const initial = legacy ? { rqdata: 'initial' } : { verifyParams: { rqdata: 'initial' } };
const updated = legacy ? { rqdata: 'current-000000000000' } : { verifyParams: { rqdata: 'current-000000000000' } };
- const fixture = await render();
+ const fixture = await render(, MOUNT_OPTIONS);
await waitForSdk('loads-1');
await fixture.rerender();
const loads = legacy ? 2 : 1;