Skip to content
Draft
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
52 changes: 24 additions & 28 deletions test/smoke/suites/debugConfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,36 +88,32 @@ export function startDebugConfigurationTests(): void {
await applicationInDirectModeButton.click();
await ElementHelper.waitPageLoad("domcontentloaded");

// Retry reading launch.json content to account for async insertion timing
let launchContent: string | null | undefined = null;
let includesHermesConfig = false;
const maxAttempts = 20; // ~10s with 500ms waits

for (let attempt = 0; attempt < maxAttempts; attempt++) {
const configurationElement = await ElementHelper.TryFindElement(
Element.configurationElementSelector,
TimeoutConstants.COMMAND_PALETTE_TIMEOUT,
);
launchContent = await configurationElement?.textContent();
if (launchContent) {
const normalized = launchContent.replace(/\s/g, "");
if (normalized.includes("DebugAndroidHermes")) {
includesHermesConfig = true;
break;
}
}
await ElementHelper.waitPageLoad("networkidle");
}

if (!launchContent) {
assert.fail("Fail to set launch file configuration.");
}
const launchContent = await ComponentHelper.waitUntil<string>(
async () => {
const configurationElement = await ElementHelper.TryFindElement(
Element.configurationElementSelector,
TimeoutConstants.COMMAND_PALETTE_TIMEOUT,
);
const text = (await configurationElement?.textContent()) || "";
const normalized = text.replace(/\s/g, "");

return {
ok: normalized.includes("DebugAndroidHermes"),
actual: normalized || "<empty>",
value: text,
};
},
{
operation: "launch.json debug configuration update",
expected: 'content includes "DebugAndroidHermes"',
timeout: TimeoutConstants.DEBUG_CONFIGURATION_TIMEOUT,
interval: 500,
},
);

assert.ok(
includesHermesConfig,
`Expected launchContent to include "Debug Android Hermes", but got: ${(
launchContent || ""
).replace(/\s/g, "")}`,
!!launchContent,
"Expected launch.json content to be available after configuration insertion.",
);
});
});
Expand Down
85 changes: 69 additions & 16 deletions test/smoke/suites/helper/componentHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,28 +98,42 @@ export class ComponentHelper {
expected: string,
timeout: number = 30000,
): Promise<void> {
const ok = await WaitHelper.waitIsTrue(async () => {
const packager = await this.getReactNativePackager();
const currentState = await packager.getAttribute("aria-label");
return !!currentState?.includes(expected);
}, timeout);
if (!ok) {
throw new Error(`Packager state did not include "${expected}" within ${timeout}ms`);
}
await this.waitUntil(
async () => {
const packager = await this.getReactNativePackager();
const currentState = (await packager.getAttribute("aria-label")) || "<empty>";
return {
ok: currentState.includes(expected),
actual: currentState,
};
},
{
operation: "packager state update",
expected: `state includes \"${expected}\"`,
timeout,
},
);
}

public static async waitPackagerStateIncludesOneOf(
expectedList: string[],
timeout: number = 30000,
): Promise<void> {
const ok = await this.isPackagerStateIncludesOneOf(expectedList, timeout);
if (!ok) {
throw new Error(
`Packager state did not include any of ${expectedList
.map(e => `"${e}"`)
.join(", ")} within ${timeout}ms`,
);
}
await this.waitUntil(
async () => {
const packager = await this.getReactNativePackager();
const currentState = (await packager.getAttribute("aria-label")) || "<empty>";
return {
ok: expectedList.some(exp => currentState.includes(exp)),
actual: currentState,
};
},
{
operation: "packager state update",
expected: `state includes one of ${expectedList.map(e => `\"${e}\"`).join(", ")}`,
timeout,
},
);
}

public static async isPackagerStateIncludesOneOf(
Expand All @@ -132,4 +146,43 @@ export class ComponentHelper {
return expectedList.some(exp => currentState?.includes(exp));
}, timeout);
}

public static async waitUntil<T>(
condition: () => Promise<{ ok: boolean; actual?: string; value?: T }>,
options: {
operation: string;
expected: string;
timeout?: number;
interval?: number;
},
): Promise<T | undefined> {
const timeout = options.timeout ?? 30000;
const interval = options.interval ?? 1000;
let lastActual = "<unavailable>";
let lastValue: T | undefined;

const ok = await WaitHelper.waitIsTrue(
async () => {
const result = await condition();
if (result.actual !== undefined) {
lastActual = result.actual;
}
if (result.value !== undefined) {
lastValue = result.value;
}

return result.ok;
},
timeout,
interval,
);

if (!ok) {
throw new Error(
`[WaitTimeout] ${options.operation}. Expected: ${options.expected}. Last actual: ${lastActual}. Timeout: ${timeout}ms. Interval: ${interval}ms.`,
);
}

return lastValue;
}
}
3 changes: 3 additions & 0 deletions test/smoke/suites/helper/timeoutConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export class TimeoutConstants {
/** Command palette visibility timeout - 5 seconds */
static readonly COMMAND_PALETTE_TIMEOUT = 5000;

/** launch.json debug configuration insertion timeout - 10 seconds */
static readonly DEBUG_CONFIGURATION_TIMEOUT = 10000;

/** Package loader timeout - 4 minutes (240 seconds) */
static readonly PACKAGE_LOADER_TIMEOUT = 4 * 60 * 1000;

Expand Down