Skip to content
Merged
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
61 changes: 53 additions & 8 deletions test/helpers/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,24 +479,69 @@ export async function getClipboardPlaintext(): Promise<string> {
return Buffer.from(b64, 'base64').toString('utf8');
}

/**
* Paste text into an iOS field via the simulator pasteboard + Paste menu.
*
* CI intermittently fails `mobile: setPasteboard` with:
* Process ended with exitcode 60 (cmd: 'xcrun simctl pbcopy <udid>')
* Retries that call, then falls back to typeText if the Paste menu never appears
* (so RestoreButton waits don't hang after a silent empty paste).
*/
export async function pasteIOSText(testId: string, text: string) {
if (!driver.isIOS) {
throw new Error('pasteIOSText can only be used on iOS devices');
}
await driver.execute('mobile: setPasteboard', {
content: text,
encoding: 'utf8',
});

const maxPasteboardAttempts = 4;
let lastPasteboardError: unknown;
for (let attempt = 1; attempt <= maxPasteboardAttempts; attempt++) {
try {
await driver.execute('mobile: setPasteboard', {
content: text,
encoding: 'utf8',
});
lastPasteboardError = undefined;
break;
} catch (err) {
lastPasteboardError = err;
const message = err instanceof Error ? err.message : String(err);
const isPbcopyFlake =
message.includes('exitcode 60') ||
message.includes('pbcopy') ||
message.includes('setPasteboard');
console.warn(
`→ pasteIOSText setPasteboard attempt ${attempt}/${maxPasteboardAttempts} failed: ${message}`
);
if (!isPbcopyFlake || attempt === maxPasteboardAttempts) {
break;
}
await sleep(500 * attempt);
}
}

if (lastPasteboardError) {
console.warn('→ pasteIOSText: pasteboard unavailable, falling back to typeText');
await typeText(testId, text);
return;
}

const el = await elementById(testId);
await el.waitForDisplayed();
await sleep(500); // Allow time for the element to settle
await el.click(); // focus the field
await sleep(200);
await el.click(); // trigger the paste menu
const pasteButton = await elementByText('Paste', 'exact');
await pasteButton.waitForDisplayed();
await pasteButton.click();
await sleep(200); // Allow time for the paste action to propagate

try {
const pasteButton = await elementByText('Paste', 'exact');
await pasteButton.waitForDisplayed({ timeout: 5_000 });
await pasteButton.click();
await sleep(200); // Allow time for the paste action to propagate
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
console.warn(`→ pasteIOSText: Paste menu unavailable (${message}); falling back to typeText`);
await typeText(testId, text);
}
}

export async function typeText(testId: string, text: string) {
Expand Down
7 changes: 5 additions & 2 deletions test/specs/migration.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,11 @@ async function restoreRnWallet(
await confirmInputOnKeyboard();
}

// Restore wallet
await tap('RestoreButton');
// Restore wallet (wait explicitly — empty/failed paste leaves RestoreButton hidden)
const restoreBtn = await elementById('RestoreButton');
await restoreBtn.waitForDisplayed({ timeout: 60_000 });
await sleep(150);
await restoreBtn.click();
await waitForSetupWalletScreenFinish();

// Wait for Get Started
Expand Down