Files
iptvnator/apps/electron-backend-e2e/src/performance/xtream-scenario-operation-observer.ts
T
4grayandClaude Opus 5.5 123bc170e9 fix(sources): own Undo timer, per-connection health history, race-free perf waits
Local review round 2 (Codex + Greptile):

- The delete committed whenever the shared snack bar was dismissed, so
  another message (a refresh finishing elsewhere) cut the four-second Undo
  window short and deleted the source early. The window is now the
  service's own timer; only the toast's close button (commitNow) or a
  second delete commits sooner.
- The health history was keyed by playlist ID only, so an edited address
  or account inherited the old connection's result, failing run included.
  Entries now keep a 32-bit FNV-1a fingerprint of the source health key
  (no URL or credential); a result for another connection starts over and
  a stale entry no longer shows.
- The M3U refresh-cancellation probe still queried the removed row's
  .cancel-btn, .busy-state__message and .action-spinner, so its cancel
  never fired; it now uses the source row's selectors (smoke run passes,
  cancelEffect=true).
- The Xtream delete benchmark waited to catch the "Removing…" row coming
  back before it left, which a fast delete could finish before the wait
  began. The driver now arms a delete-playlist operation window before the
  commit click and ends on its completed event and the absent row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:03:44 +02:00

253 lines
9.2 KiB
TypeScript

import type { Page } from '@playwright/test';
const OPERATION_STATE_KEY = '__iptvnatorXtreamScenarioOperationWindow';
const DELETE_OPERATION = 'delete-xtream-content';
/** A content delete before a refresh, or a whole playlist's delete. */
export type XtreamObservedOperation =
typeof DELETE_OPERATION | 'delete-playlist';
const OPERATION_TIMEOUT_MS = 120_000;
export interface XtreamOperationWindowSnapshot<
Operation extends XtreamObservedOperation = typeof DELETE_OPERATION,
> {
readonly active: boolean;
readonly operation: Operation;
readonly operationId: string;
readonly playlistId: string;
readonly startedEpochMs: number;
readonly terminalEpochMs: number | null;
readonly terminalStatus: 'cancelled' | 'completed' | 'error' | null;
}
export interface XtreamOperationWindow<
Operation extends XtreamObservedOperation = typeof DELETE_OPERATION,
> {
assertActive(): Promise<XtreamOperationWindowSnapshot<Operation>>;
dispose(): Promise<void>;
waitForActive(): Promise<XtreamOperationWindowSnapshot<Operation>>;
waitForTerminal(): Promise<XtreamOperationWindowSnapshot<Operation>>;
}
export async function installXtreamOperationWindow<
Operation extends XtreamObservedOperation = typeof DELETE_OPERATION,
>(
page: Page,
operation: Operation = DELETE_OPERATION as Operation
): Promise<XtreamOperationWindow<Operation>> {
await page.evaluate(
({ command, operation, stateKey }) => {
interface State {
active: boolean;
operation: string;
operationId: string | null;
playlistId: string | null;
startedEpochMs: number | null;
terminalEpochMs: number | null;
terminalStatus: string | null;
unsubscribe: () => void;
}
const target = globalThis as unknown as Record<string, unknown>;
if (command !== 'install-operation' || target[stateKey]) {
throw new Error('xtream-operation-window-state-invalid');
}
const electron = target['electron'] as
| {
onDbOperationEvent?: (
callback: (event: Record<string, unknown>) => void
) => () => void;
}
| undefined;
if (typeof electron?.onDbOperationEvent !== 'function') {
throw new Error('xtream-db-operation-listener-unavailable');
}
const epoch = (): number =>
performance.timeOrigin + performance.now();
const state: State = {
active: false,
operation,
operationId: null,
playlistId: null,
startedEpochMs: null,
terminalEpochMs: null,
terminalStatus: null,
unsubscribe: () => undefined,
};
state.unsubscribe = electron.onDbOperationEvent((event) => {
if (event['operation'] !== operation) return;
const status = event['status'];
if (status === 'started' && state.startedEpochMs === null) {
state.active = true;
state.startedEpochMs = epoch();
state.operationId =
typeof event['operationId'] === 'string'
? event['operationId']
: null;
state.playlistId =
typeof event['playlistId'] === 'string'
? event['playlistId']
: null;
return;
}
if (
!state.active ||
!['cancelled', 'completed', 'error'].includes(
String(status)
)
) {
return;
}
const eventOperationId =
typeof event['operationId'] === 'string'
? event['operationId']
: null;
if (
state.operationId !== null &&
eventOperationId !== state.operationId
) {
return;
}
state.active = false;
state.terminalEpochMs = epoch();
state.terminalStatus = String(status);
});
target[stateKey] = state;
},
{
command: 'install-operation',
operation,
stateKey: OPERATION_STATE_KEY,
}
);
return {
assertActive: () => assertOperationActive(page, operation),
dispose: () => disposeOperationState(page),
async waitForActive() {
await waitForOperationState(page, 'operation-active');
return assertOperationActive(page, operation);
},
async waitForTerminal() {
await waitForOperationState(page, 'operation-terminal');
return readOperationSnapshot(page, operation);
},
};
}
async function waitForOperationState(
page: Page,
waitFor: 'operation-active' | 'operation-terminal'
): Promise<void> {
await page.waitForFunction(
({ stateKey, waitFor: condition }) => {
const target = globalThis as unknown as Record<string, unknown>;
const state = target[stateKey] as
| {
active?: boolean;
terminalEpochMs?: number | null;
}
| undefined;
return condition === 'operation-active'
? state?.active === true
: typeof state?.terminalEpochMs === 'number';
},
{ stateKey: OPERATION_STATE_KEY, waitFor },
{ timeout: OPERATION_TIMEOUT_MS }
);
}
async function assertOperationActive<Operation extends XtreamObservedOperation>(
page: Page,
operation: Operation
): Promise<XtreamOperationWindowSnapshot<Operation>> {
const snapshot = await readOperationSnapshot(page, operation);
if (!snapshot.active || snapshot.terminalEpochMs !== null) {
throw new Error('xtream-background-action-outside-delete-window');
}
return snapshot;
}
async function readOperationSnapshot<Operation extends XtreamObservedOperation>(
page: Page,
operation: Operation
): Promise<XtreamOperationWindowSnapshot<Operation>> {
const value = await page.evaluate(
({ stateKey }) => {
const target = globalThis as unknown as Record<string, unknown>;
const state = target[stateKey];
if (!state) throw new Error('xtream-operation-window-missing');
const {
active,
operation,
operationId,
playlistId,
startedEpochMs,
terminalEpochMs,
terminalStatus,
} = state as Record<string, unknown>;
return {
active,
operation,
operationId,
playlistId,
startedEpochMs,
terminalEpochMs,
terminalStatus,
};
},
{ command: 'snapshot-operation', stateKey: OPERATION_STATE_KEY }
);
if (!isOperationSnapshot(value, operation)) {
throw new Error('xtream-operation-window-invalid');
}
return value;
}
async function disposeOperationState(page: Page): Promise<void> {
await page.evaluate(
({ stateKey }) => {
const target = globalThis as unknown as Record<string, unknown>;
const state = target[stateKey] as
{ unsubscribe?: () => void } | undefined;
state?.unsubscribe?.();
delete target[stateKey];
},
{ command: 'dispose-observer', stateKey: OPERATION_STATE_KEY }
);
}
function isOperationSnapshot<Operation extends XtreamObservedOperation>(
value: unknown,
operation: Operation
): value is XtreamOperationWindowSnapshot<Operation> {
if (!isRecord(value)) return false;
const active = value['active'];
const startedEpochMs = value['startedEpochMs'];
const terminalEpochMs = value['terminalEpochMs'];
const terminalStatus = value['terminalStatus'];
return (
value['operation'] === operation &&
typeof active === 'boolean' &&
finitePositive(startedEpochMs) &&
nonEmptyString(value['operationId']) &&
nonEmptyString(value['playlistId']) &&
(active
? terminalEpochMs === null && terminalStatus === null
: finitePositive(terminalEpochMs) &&
terminalEpochMs >= startedEpochMs &&
['cancelled', 'completed', 'error'].includes(
String(terminalStatus)
))
);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
function finitePositive(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value) && value > 0;
}
function nonEmptyString(value: unknown): value is string {
return typeof value === 'string' && value.length > 0;
}