mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(browser): unify worker target admission across capture workflows
This commit is contained in:
1 parent
cadd5b75b3
commit
a5ad164283
6 files changed
+253
-107
No files matched your search
@@ -5,13 +5,16 @@ import type {
|
|||||||
} from "../domain/javascript/electronObservation.js";
|
} from "../domain/javascript/electronObservation.js";
|
||||||
import type { CdpConnection } from "./CdpConnection.js";
|
import type { CdpConnection } from "./CdpConnection.js";
|
||||||
import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
|
import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
|
||||||
import { recordValue, cdpStringValue } from "./CdpCaptureValues.js";
|
import { recordValue } from "./CdpCaptureValues.js";
|
||||||
import type { CdpEndpointTarget } from "./CdpEndpoint.js";
|
import type { CdpEndpointTarget } from "./CdpEndpoint.js";
|
||||||
import { optionalCdpCommand } from "./CdpOptionalCommand.js";
|
import { optionalCdpCommand } from "./CdpOptionalCommand.js";
|
||||||
import { authorizedElectronFile } from "./ElectronFileScope.js";
|
import { authorizedElectronFile } from "./ElectronFileScope.js";
|
||||||
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
|
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
|
||||||
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
||||||
import { workerTargetOwnership } from "./CdpWorkerTargetOwnership.js";
|
import {
|
||||||
|
parseCdpWorkerTarget,
|
||||||
|
workerTargetOwnership,
|
||||||
|
} from "./CdpWorkerTarget.js";
|
||||||
|
|
||||||
/** Inventory local worker targets without attaching to them. */
|
/** Inventory local worker targets without attaching to them. */
|
||||||
export const captureElectronWorkers = async (input: {
|
export const captureElectronWorkers = async (input: {
|
||||||
@@ -47,33 +50,27 @@ export const captureElectronWorkers = async (input: {
|
|||||||
}
|
}
|
||||||
for (const entry of targetInfos) {
|
for (const entry of targetInfos) {
|
||||||
throwIfElectronWorkerCaptureCancelled(input.signal);
|
throwIfElectronWorkerCaptureCancelled(input.signal);
|
||||||
const target = recordValue(entry);
|
const parsed = parseCdpWorkerTarget(entry);
|
||||||
if (target === undefined) {
|
if (!parsed.ok) {
|
||||||
input.completeness.exclude("workers", "invalid_protocol_value");
|
input.completeness.exclude("workers", "invalid_protocol_value");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const type = cdpStringValue(target.type) ?? "";
|
const target = parsed.value;
|
||||||
if (!type.includes("worker")) continue;
|
if (target.kind === "non_worker") continue;
|
||||||
const ownership = workerTargetOwnership(
|
const ownership = workerTargetOwnership(
|
||||||
target,
|
target,
|
||||||
input.target.id,
|
input.target.id,
|
||||||
input.frameIds,
|
input.frameIds,
|
||||||
);
|
);
|
||||||
if (!ownership.ok) {
|
if (ownership !== "selected") {
|
||||||
input.completeness.exclude("workers", "invalid_protocol_value");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ownership.value.scope !== "selected") {
|
|
||||||
input.completeness.exclude(
|
input.completeness.exclude(
|
||||||
"workers",
|
"workers",
|
||||||
ownership.value.scope === "unknown"
|
ownership === "unknown" ? "unattributed_target" : "out_of_target_scope",
|
||||||
? "unattributed_target"
|
|
||||||
: "out_of_target_scope",
|
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const path = await authorizedElectronFile(
|
const path = await authorizedElectronFile(
|
||||||
cdpStringValue(target.url) ?? "",
|
target.url,
|
||||||
input.signal,
|
input.signal,
|
||||||
"inspect_electron_page",
|
"inspect_electron_page",
|
||||||
input.asarHeaderAdmission,
|
input.asarHeaderAdmission,
|
||||||
@@ -82,22 +79,13 @@ export const captureElectronWorkers = async (input: {
|
|||||||
input.completeness.exclude("workers", "out_of_target_scope");
|
input.completeness.exclude("workers", "out_of_target_scope");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const targetId = boundedTargetField(target.targetId);
|
|
||||||
if (targetId === null) {
|
|
||||||
input.completeness.exclude("workers", "invalid_protocol_value");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (typeof target.attached !== "boolean") {
|
|
||||||
input.completeness.exclude("workers", "invalid_protocol_value");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
workers.push({
|
workers.push({
|
||||||
target_id: targetId,
|
target_id: target.targetId,
|
||||||
type,
|
type: target.type,
|
||||||
file_path: path,
|
file_path: path,
|
||||||
attached: target.attached,
|
attached: target.attached,
|
||||||
opener_target_id: ownership.value.openerTargetId,
|
opener_target_id: target.openerTargetId,
|
||||||
parent_frame_id: ownership.value.parentFrameId,
|
parent_frame_id: target.parentFrameId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return workers.sort((left, right) =>
|
return workers.sort((left, right) =>
|
||||||
@@ -111,8 +99,3 @@ const throwIfElectronWorkerCaptureCancelled = (signal?: AbortSignal): void => {
|
|||||||
cause: signal.reason,
|
cause: signal.reason,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const boundedTargetField = (value: unknown): string | null => {
|
|
||||||
const field = cdpStringValue(value);
|
|
||||||
return field === undefined || field === "" ? null : field;
|
|
||||||
};
|
|
||||||
@@ -9,7 +9,7 @@ import type { CaptureContext } from "./CdpPageCapture.js";
|
|||||||
|
|
||||||
const origin = "http://127.0.0.1:43127";
|
const origin = "http://127.0.0.1:43127";
|
||||||
|
|
||||||
const capture = async (inventory: unknown) => {
|
const capture = async (inventory: unknown, frameIds = new Set<string>()) => {
|
||||||
const input = inspectWebPageInputSchema.parse({
|
const input = inspectWebPageInputSchema.parse({
|
||||||
cdp_endpoint: origin,
|
cdp_endpoint: origin,
|
||||||
allowed_origins: [origin],
|
allowed_origins: [origin],
|
||||||
@@ -38,7 +38,7 @@ const capture = async (inventory: unknown) => {
|
|||||||
limitations: [],
|
limitations: [],
|
||||||
events,
|
events,
|
||||||
},
|
},
|
||||||
new Set(),
|
frameIds,
|
||||||
);
|
);
|
||||||
return { workers, completeness: events.completeness.snapshot() };
|
return { workers, completeness: events.completeness.snapshot() };
|
||||||
};
|
};
|
||||||
@@ -199,3 +199,74 @@ it.each([{ openerId: "" }, { parentFrameId: null }])(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{ type: undefined },
|
||||||
|
{ type: 7 },
|
||||||
|
{ type: "" },
|
||||||
|
{ targetId: "" },
|
||||||
|
{ url: undefined },
|
||||||
|
{ attached: null },
|
||||||
|
{ openerId: null },
|
||||||
|
{ parentFrameId: "" },
|
||||||
|
])(
|
||||||
|
"reports malformed worker facts before ownership and URL policy: %j",
|
||||||
|
async (override) => {
|
||||||
|
const result = await capture({
|
||||||
|
targetInfos: [
|
||||||
|
{
|
||||||
|
targetId: "worker",
|
||||||
|
type: "worker",
|
||||||
|
url: "https://outside.example/worker.js",
|
||||||
|
attached: false,
|
||||||
|
openerId: "foreign-page",
|
||||||
|
...override,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(result.workers).toEqual([]);
|
||||||
|
expect(result.completeness.excluded).toEqual([
|
||||||
|
{ section: "workers", reason: "invalid_protocol_value", count: 1 },
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("uses parsed frame relationships without treating valid non-workers as malformed", async () => {
|
||||||
|
const result = await capture(
|
||||||
|
{
|
||||||
|
targetInfos: [
|
||||||
|
{ type: "page" },
|
||||||
|
{
|
||||||
|
targetId: "worker",
|
||||||
|
type: "shared_worker",
|
||||||
|
url: `${origin}/worker.js`,
|
||||||
|
attached: false,
|
||||||
|
openerId: "foreign-page",
|
||||||
|
parentFrameId: "selected-frame",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targetId: "foreign",
|
||||||
|
type: "worker",
|
||||||
|
url: `${origin}/foreign.js`,
|
||||||
|
attached: true,
|
||||||
|
parentFrameId: "foreign-frame",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
new Set(["selected-frame"]),
|
||||||
|
);
|
||||||
|
expect(result.workers).toEqual([
|
||||||
|
{
|
||||||
|
target_id: "worker",
|
||||||
|
type: "shared_worker",
|
||||||
|
url: `${origin}/worker.js`,
|
||||||
|
origin,
|
||||||
|
attached: false,
|
||||||
|
opener_target_id: "foreign-page",
|
||||||
|
parent_frame_id: "selected-frame",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(result.completeness.excluded).toEqual([
|
||||||
|
{ section: "workers", reason: "out_of_target_scope", count: 1 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -4,11 +4,13 @@ import {
|
|||||||
admitRecords,
|
admitRecords,
|
||||||
allowedSanitizedUrl,
|
allowedSanitizedUrl,
|
||||||
recordValue,
|
recordValue,
|
||||||
cdpStringValue,
|
|
||||||
} from "./CdpCaptureValues.js";
|
} from "./CdpCaptureValues.js";
|
||||||
import type { CaptureContext } from "./CdpPageCapture.js";
|
import type { CaptureContext } from "./CdpPageCapture.js";
|
||||||
import { optionalCdpCommand } from "./CdpOptionalCommand.js";
|
import { optionalCdpCommand } from "./CdpOptionalCommand.js";
|
||||||
import { workerTargetOwnership } from "./CdpWorkerTargetOwnership.js";
|
import {
|
||||||
|
parseCdpWorkerTarget,
|
||||||
|
workerTargetOwnership,
|
||||||
|
} from "./CdpWorkerTarget.js";
|
||||||
import { exclusionReasonForUrl } from "./CdpCaptureEventHelpers.js";
|
import { exclusionReasonForUrl } from "./CdpCaptureEventHelpers.js";
|
||||||
|
|
||||||
interface CaptureWorkersInput {
|
interface CaptureWorkersInput {
|
||||||
@@ -42,59 +44,38 @@ export const captureWorkers = async (
|
|||||||
}
|
}
|
||||||
let malformedEntries = 0;
|
let malformedEntries = 0;
|
||||||
for (const entry of targets ?? []) {
|
for (const entry of targets ?? []) {
|
||||||
const target = recordValue(entry);
|
const parsed = parseCdpWorkerTarget(entry);
|
||||||
if (target === undefined) {
|
if (!parsed.ok) {
|
||||||
malformedEntries += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const type = cdpStringValue(target.type);
|
|
||||||
if (type === undefined || type.length === 0) {
|
|
||||||
malformedEntries += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!type.includes("worker")) continue;
|
|
||||||
const targetId = cdpStringValue(target.targetId);
|
|
||||||
if (
|
|
||||||
targetId === undefined ||
|
|
||||||
targetId.length === 0 ||
|
|
||||||
typeof target.attached !== "boolean" ||
|
|
||||||
typeof target.url !== "string"
|
|
||||||
) {
|
|
||||||
malformedEntries += 1;
|
malformedEntries += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const target = parsed.value;
|
||||||
|
if (target.kind === "non_worker") continue;
|
||||||
const ownership = workerTargetOwnership(
|
const ownership = workerTargetOwnership(
|
||||||
target,
|
target,
|
||||||
context.target.id,
|
context.target.id,
|
||||||
frameIds,
|
frameIds,
|
||||||
);
|
);
|
||||||
if (!ownership.ok) {
|
if (ownership !== "selected") {
|
||||||
malformedEntries += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ownership.value.scope !== "selected") {
|
|
||||||
completeness.exclude(
|
completeness.exclude(
|
||||||
"workers",
|
"workers",
|
||||||
ownership.value.scope === "unknown"
|
ownership === "unknown" ? "unattributed_target" : "out_of_target_scope",
|
||||||
? "unattributed_target"
|
|
||||||
: "out_of_target_scope",
|
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const url = allowedSanitizedUrl(target.url, allowedOrigins);
|
const url = allowedSanitizedUrl(target.url, allowedOrigins);
|
||||||
if (url === undefined) {
|
if (url === undefined) {
|
||||||
const rawUrl = cdpStringValue(target.url);
|
completeness.exclude("workers", exclusionReasonForUrl(target.url));
|
||||||
completeness.exclude("workers", exclusionReasonForUrl(rawUrl));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
items.push({
|
items.push({
|
||||||
target_id: targetId,
|
target_id: target.targetId,
|
||||||
type,
|
type: target.type,
|
||||||
url: url.url,
|
url: url.url,
|
||||||
origin: url.origin,
|
origin: url.origin,
|
||||||
attached: target.attached,
|
attached: target.attached,
|
||||||
opener_target_id: ownership.value.openerTargetId,
|
opener_target_id: target.openerTargetId,
|
||||||
parent_frame_id: ownership.value.parentFrameId,
|
parent_frame_id: target.parentFrameId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (malformedEntries > 0)
|
if (malformedEntries > 0)
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { err, ok, type Result } from "../domain/result.js";
|
||||||
|
import { cdpStringValue, recordValue } from "./CdpCaptureValues.js";
|
||||||
|
|
||||||
|
export interface CdpWorkerRelationships {
|
||||||
|
readonly openerTargetId: string | null;
|
||||||
|
readonly parentFrameId: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CdpWorkerTarget extends CdpWorkerRelationships {
|
||||||
|
readonly kind: "worker";
|
||||||
|
readonly targetId: string;
|
||||||
|
readonly type: string;
|
||||||
|
readonly url: string;
|
||||||
|
readonly attached: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Admit protocol facts before either consumer applies target or URL policy. */
|
||||||
|
export const parseCdpWorkerTarget = (
|
||||||
|
input: unknown,
|
||||||
|
): Result<
|
||||||
|
CdpWorkerTarget | { readonly kind: "non_worker" },
|
||||||
|
{ readonly _tag: "InvalidWorkerTarget" }
|
||||||
|
> => {
|
||||||
|
const target = recordValue(input);
|
||||||
|
const type = cdpStringValue(target?.type);
|
||||||
|
if (target === undefined || type === undefined || type === "")
|
||||||
|
return err({ _tag: "InvalidWorkerTarget" });
|
||||||
|
if (!type.includes("worker")) return ok({ kind: "non_worker" });
|
||||||
|
const targetId = cdpStringValue(target.targetId);
|
||||||
|
const url = cdpStringValue(target.url);
|
||||||
|
const openerTargetId = cdpStringValue(target.openerId);
|
||||||
|
const parentFrameId = cdpStringValue(target.parentFrameId);
|
||||||
|
if (
|
||||||
|
targetId === undefined ||
|
||||||
|
targetId === "" ||
|
||||||
|
url === undefined ||
|
||||||
|
typeof target.attached !== "boolean" ||
|
||||||
|
(target.openerId !== undefined &&
|
||||||
|
(openerTargetId === undefined || openerTargetId === "")) ||
|
||||||
|
(target.parentFrameId !== undefined &&
|
||||||
|
(parentFrameId === undefined || parentFrameId === ""))
|
||||||
|
)
|
||||||
|
return err({ _tag: "InvalidWorkerTarget" });
|
||||||
|
return ok({
|
||||||
|
kind: "worker",
|
||||||
|
targetId,
|
||||||
|
type,
|
||||||
|
url,
|
||||||
|
attached: target.attached,
|
||||||
|
openerTargetId: openerTargetId ?? null,
|
||||||
|
parentFrameId: parentFrameId ?? null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Optional CDP relationships establish ownership only when supplied. */
|
||||||
|
export const workerTargetOwnership = (
|
||||||
|
relationships: CdpWorkerRelationships,
|
||||||
|
selectedTargetId: string,
|
||||||
|
frameIds: ReadonlySet<string>,
|
||||||
|
): "selected" | "foreign" | "unknown" => {
|
||||||
|
const { openerTargetId, parentFrameId } = relationships;
|
||||||
|
if (
|
||||||
|
openerTargetId === selectedTargetId ||
|
||||||
|
(parentFrameId !== null && frameIds.has(parentFrameId))
|
||||||
|
)
|
||||||
|
return "selected";
|
||||||
|
return openerTargetId === null && parentFrameId === null
|
||||||
|
? "unknown"
|
||||||
|
: "foreign";
|
||||||
|
};
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { err, ok, type Result } from "../domain/result.js";
|
|
||||||
import { cdpStringValue } from "./CdpCaptureValues.js";
|
|
||||||
|
|
||||||
/** Optional CDP relationships establish ownership only when supplied. */
|
|
||||||
export const workerTargetOwnership = (
|
|
||||||
target: Readonly<Record<string, unknown>>,
|
|
||||||
selectedTargetId: string,
|
|
||||||
frameIds: ReadonlySet<string>,
|
|
||||||
): Result<
|
|
||||||
{
|
|
||||||
readonly scope: "selected" | "foreign" | "unknown";
|
|
||||||
readonly openerTargetId: string | null;
|
|
||||||
readonly parentFrameId: string | null;
|
|
||||||
},
|
|
||||||
{ readonly _tag: "InvalidWorkerRelationship" }
|
|
||||||
> => {
|
|
||||||
const openerTargetId = cdpStringValue(target.openerId);
|
|
||||||
const parentFrameId = cdpStringValue(target.parentFrameId);
|
|
||||||
if (
|
|
||||||
(target.openerId !== undefined &&
|
|
||||||
(openerTargetId === undefined || openerTargetId === "")) ||
|
|
||||||
(target.parentFrameId !== undefined &&
|
|
||||||
(parentFrameId === undefined || parentFrameId === ""))
|
|
||||||
)
|
|
||||||
return err({ _tag: "InvalidWorkerRelationship" });
|
|
||||||
const selected =
|
|
||||||
openerTargetId === selectedTargetId ||
|
|
||||||
(parentFrameId !== undefined && frameIds.has(parentFrameId));
|
|
||||||
return ok({
|
|
||||||
scope: selected
|
|
||||||
? "selected"
|
|
||||||
: openerTargetId === undefined && parentFrameId === undefined
|
|
||||||
? "unknown"
|
|
||||||
: "foreign",
|
|
||||||
openerTargetId: openerTargetId ?? null,
|
|
||||||
parentFrameId: parentFrameId ?? null,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -849,6 +849,85 @@ describe("CdpElectronProvider malformed inventory replies", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["missing type", { type: undefined }],
|
||||||
|
["non-string type", { type: 7 }],
|
||||||
|
["empty type", { type: "" }],
|
||||||
|
["missing target identity", { targetId: undefined }],
|
||||||
|
["non-string target identity", { targetId: 7 }],
|
||||||
|
["empty target identity", { targetId: "" }],
|
||||||
|
["missing URL", { url: undefined }],
|
||||||
|
["non-string URL", { url: 7 }],
|
||||||
|
["missing attached state", { attached: undefined }],
|
||||||
|
["non-boolean attached state", { attached: "false" }],
|
||||||
|
["non-string opener relationship", { openerId: 7 }],
|
||||||
|
["empty opener relationship", { openerId: "" }],
|
||||||
|
["non-string frame relationship", { parentFrameId: 7 }],
|
||||||
|
["empty frame relationship", { parentFrameId: "" }],
|
||||||
|
] as const)(
|
||||||
|
"retains valid workers while reporting %s before target policy",
|
||||||
|
async (_kind, malformed) => {
|
||||||
|
const inspected = await inspectWith("Target.getTargets", (fileUrl) => ({
|
||||||
|
targetInfos: [
|
||||||
|
{
|
||||||
|
targetId: "selected-worker",
|
||||||
|
type: "worker",
|
||||||
|
url: fileUrl,
|
||||||
|
attached: false,
|
||||||
|
openerId: "electron-page",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targetId: "malformed-worker",
|
||||||
|
type: "worker",
|
||||||
|
url: fileUrl,
|
||||||
|
attached: false,
|
||||||
|
openerId: "foreign-page",
|
||||||
|
...malformed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targetId: "foreign-worker",
|
||||||
|
type: "shared_worker",
|
||||||
|
url: fileUrl,
|
||||||
|
attached: true,
|
||||||
|
openerId: "foreign-page",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targetId: "unattributed-worker",
|
||||||
|
type: "service_worker",
|
||||||
|
url: fileUrl,
|
||||||
|
attached: false,
|
||||||
|
},
|
||||||
|
// Non-worker rows do not need worker identity or relationship fields.
|
||||||
|
{ type: "page", targetId: 7, openerId: null },
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
expect(inspected.workers).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
target_id: "selected-worker",
|
||||||
|
type: "worker",
|
||||||
|
attached: false,
|
||||||
|
opener_target_id: "electron-page",
|
||||||
|
parent_frame_id: null,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
const workerExclusions = inspected.completeness.excluded.filter(
|
||||||
|
({ section }) => section === "workers",
|
||||||
|
);
|
||||||
|
expect(workerExclusions).toHaveLength(3);
|
||||||
|
expect(workerExclusions).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{ section: "workers", reason: "invalid_protocol_value", count: 1 },
|
||||||
|
{ section: "workers", reason: "out_of_target_scope", count: 1 },
|
||||||
|
{ section: "workers", reason: "unattributed_target", count: 1 },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(inspected.completeness.unavailable_sections).toContain("workers");
|
||||||
|
expect(inspected.completeness.policy_filtered_sections).toContain(
|
||||||
|
"workers",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
["dom", "DOMSnapshot.captureSnapshot", () => ({ strings: [] })],
|
["dom", "DOMSnapshot.captureSnapshot", () => ({ strings: [] })],
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in new issue
Block a user