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
59 changes: 30 additions & 29 deletions test/helpers/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,32 +201,15 @@ export async function expectTextWithin(
strategy?: RetrieveStrategy;
} = {}
) {
const parent = elementById(ancestorId);
await parent.waitForDisplayed({ timeout });

if (driver.isIOS) {
const parentLabel = await parent.getAttribute('label');
const parentValue = await parent.getAttribute('value');
const matchesParent = (() => {
if (strategy === 'exact') {
return (
(typeof parentLabel === 'string' && parentLabel === text) ||
(typeof parentValue === 'string' && parentValue === text)
);
}
return (
(typeof parentLabel === 'string' && parentLabel.includes(text)) ||
(typeof parentValue === 'string' && parentValue.includes(text))
);
})();

if (matchesParent) {
if (!visible) {
await parent.waitForDisplayed({ reverse: true, timeout });
}
return;
}
}
const matchesParent = async (parent: ChainablePromiseElement) => {
if (!driver.isIOS) return false;
const label = await parent.getAttribute('label');
const value = await parent.getAttribute('value');
return [label, value].some(
(value) =>
typeof value === 'string' && (strategy === 'exact' ? value === text : value.includes(text))
);
};

const needle = (() => {
if (driver.isAndroid) {
Expand All @@ -238,10 +221,28 @@ export async function expectTextWithin(
: `.//*[self::XCUIElementTypeStaticText or self::XCUIElementTypeTextView or self::XCUIElementTypeTextField or self::XCUIElementTypeButton][contains(@label,'${text}') or contains(@value,'${text}')]`;
})();

if (!visible) {
await parent.$(needle).waitForDisplayed({ reverse: true, timeout });
const parent = elementById(ancestorId);
await parent.waitForDisplayed({ timeout });

if (visible) {
await browser.waitUntil(
async () => {
// A list-position identifier can move to another element while its contents load.
const currentParent = elementById(ancestorId);
if (!(await currentParent.isDisplayed())) return false;
return (
(await matchesParent(currentParent)) || (await currentParent.$(needle).isDisplayed())
);
},
{ timeout, timeoutMsg: `Expected "${text}" within "${ancestorId}"` }
);
return;
}

if (await matchesParent(parent)) {
await parent.waitForDisplayed({ reverse: true, timeout });
} else {
await parent.$(needle).waitForDisplayed({ timeout });
await parent.$(needle).waitForDisplayed({ reverse: true, timeout });
}
}

Expand Down
4 changes: 1 addition & 3 deletions test/specs/lightning.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,7 @@ describe('@lightning @ios_gate - Lightning', () => {
await mineBlocks(6);
await electrum?.waitForSync();
await elementById('Channel').waitForDisplayed({ reverse: true });
if (driver.isAndroid) {
await tap('NavigationBack');
}
await tap('NavigationBack');
await doNavigationClose();

await expectTextWithin('ActivityShort-0', '9 000');
Expand Down
79 changes: 79 additions & 0 deletions test/unit/activity-text.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { expectTextWithin } from '../helpers/actions.ts';

function changingRow({ isAndroid = false, missing = false, removing = false } = {}) {
const original = { label: '', children: removing ? ['9 000'] : ['1 000'] };
let current = original;
let queries = 0;
const waitUntil = async (probe: () => Promise<boolean>) => {
for (let attempt = 0; attempt < 3; attempt++) {
if (await probe()) return;
if (removing) {
original.children = [];
} else {
current = missing
? { label: '9 0000', children: [] }
: isAndroid
? { label: '', children: ['Transfer + 9 000'] }
: { label: '9 000', children: [] };
}
}
throw new Error('Text did not appear before the timeout');
};
const select = () => {
queries++;
const row = current;
return {
isDisplayed: async () => true,
waitForDisplayed: async () => {},
getAttribute: async (attribute: string) => (attribute === 'label' ? row.label : null),
$: () => ({
isDisplayed: async () => row.children.some((text) => text.includes('9 000')),
waitForDisplayed: async ({ reverse = false } = {}) =>
waitUntil(async () => row.children.some((text) => text.includes('9 000')) !== reverse),
}),
};
};
return {
$: select,
browser: { waitUntil },
driver: { isAndroid, isIOS: !isAndroid },
queries: () => queries,
};
}

for (const isAndroid of [false, true]) {
test(`${isAndroid ? 'Android' : 'iOS'}: follows the row currently holding the activity identifier`, async (t) => {
const previous = { browser: globalThis.browser, driver: globalThis.driver, $: globalThis.$ };
const screen = changingRow({ isAndroid });
Object.assign(globalThis, { browser: screen.browser, driver: screen.driver, $: screen.$ });
t.after(() => Object.assign(globalThis, previous));

await expectTextWithin('ActivityShort-0', '9 000', {
strategy: isAndroid ? 'contains' : 'exact',
});
assert.ok(screen.queries() > 1);
});
}

test('keeps exact matching and fails when the requested text never appears', async (t) => {
const previous = { browser: globalThis.browser, driver: globalThis.driver, $: globalThis.$ };
const screen = changingRow({ missing: true });
Object.assign(globalThis, { browser: screen.browser, driver: screen.driver, $: screen.$ });
t.after(() => Object.assign(globalThis, previous));

await assert.rejects(
expectTextWithin('ActivityShort-0', '9 000', { strategy: 'exact' }),
/timeout/
);
});

test('still waits for a descendant to disappear', async (t) => {
const previous = { browser: globalThis.browser, driver: globalThis.driver, $: globalThis.$ };
const screen = changingRow({ removing: true });
Object.assign(globalThis, { browser: screen.browser, driver: screen.driver, $: screen.$ });
t.after(() => Object.assign(globalThis, previous));

await expectTextWithin('ActivityShort-0', '9 000', { visible: false });
});