@red-hat-developer-hub/e2e-test-utils 2.1.7 → 2.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  plugins:
2
- - package: oci://registry.access.redhat.com/rhdh/backstage-community-plugin-catalog-backend-module-keycloak:{{inherit}}
2
+ - package: oci://quay.io/rhdh/backstage-community-plugin-catalog-backend-module-keycloak:{{inherit}}
3
3
  disabled: false
4
4
  pluginConfig:
5
5
  catalog:
@@ -1,5 +1,5 @@
1
1
  includes:
2
2
  - dynamic-plugins.default.yaml
3
3
  plugins:
4
- - package: oci://registry.access.redhat.com/rhdh/red-hat-developer-hub-backstage-plugin-quickstart:{{inherit}}
4
+ - package: oci://quay.io/rhdh/red-hat-developer-hub-backstage-plugin-quickstart:{{inherit}}
5
5
  disabled: true
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The whole `__coverage__` object, keyed by each instrumented file's path.
3
+ *
4
+ * Named for what it holds: istanbul's own `CoverageData` is the entry for a
5
+ * single file, so calling this that would read as one file's counters.
6
+ *
7
+ * The values stay `unknown` on purpose — this module serializes them and never
8
+ * reads inside, so modelling istanbul's report shape would buy nothing and pull
9
+ * a dependency on its types into a package that has none.
10
+ */
11
+ export type CoverageMap = Record<string, unknown>;
12
+ export type PageLike = {
13
+ isClosed(): boolean;
14
+ evaluate(pageFunction: () => CoverageMap | undefined): Promise<CoverageMap | undefined>;
15
+ };
16
+ export type BrowserLike = {
17
+ contexts(): readonly {
18
+ pages(): readonly PageLike[];
19
+ }[];
20
+ };
21
+ /** One page's coverage, tagged so repeated reads of it can overwrite. */
22
+ export type CoverageEntry = {
23
+ pageId: string;
24
+ coverage: CoverageMap;
25
+ };
26
+ /**
27
+ * Every non-empty coverage map reachable in the browser, one entry per page.
28
+ *
29
+ * Only the plugin under test is instrumented, so `__coverage__` appears solely
30
+ * in pages that loaded its bundle. Which page that is depends on how the spec
31
+ * is written — some drive Playwright's `page` fixture, others open their own
32
+ * context in `beforeAll` and run every test there — so this walks every open
33
+ * context rather than assuming one.
34
+ *
35
+ * A page carrying an empty object is dropped: it is as useless as a page with
36
+ * none, and reporting it as a find would mask the "nothing was instrumented"
37
+ * case the caller reports on.
38
+ *
39
+ * Failures are handled at two levels on purpose. A single unusable page is
40
+ * skipped, because the other pages' coverage is still worth having. A failure
41
+ * reaching the browser propagates: returning an empty array would be
42
+ * indistinguishable from "nothing was instrumented", and the caller can only
43
+ * report accurately what it is allowed to see.
44
+ */
45
+ export declare function collectCoverageFromBrowser(browser: BrowserLike): Promise<CoverageEntry[]>;
46
+ /** Every reason collection can legitimately come back with nothing. */
47
+ export declare const NO_COVERAGE_FOUND_MESSAGE: string;
48
+ /**
49
+ * Collect and persist one JSON per covered page, reporting instead of throwing.
50
+ *
51
+ * Called from fixture teardown for every test, so it must never raise: an
52
+ * unwritable output directory or a browser that died mid-test would otherwise
53
+ * fail a test that had already passed. Coverage is diagnostics, not a verdict.
54
+ * `report` is how a problem stays visible without becoming one — silence is the
55
+ * failure mode this module exists to remove.
56
+ *
57
+ * Files are named per page, not per test, and deliberately overwritten. A
58
+ * context opened in `beforeAll` outlives the whole file, and `__coverage__`
59
+ * accumulates in it, so naming per test would write a growing copy of the same
60
+ * map once per test — 30 near-duplicates of a multi-megabyte report, all summed
61
+ * again by `nyc merge`. Overwriting keeps the last and most complete read, and
62
+ * costs nothing for a per-test fixture page, which is a new page with a new id
63
+ * every time. It also collapses the retry case, where a failed and a passing
64
+ * attempt would otherwise both be counted.
65
+ */
66
+ export declare function collectAndWriteCoverage(browser: BrowserLike, target: {
67
+ dir: string;
68
+ runId: string;
69
+ }, report: (detail: string) => void): Promise<void>;
70
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/playwright/coverage.ts"],"names":[],"mappings":"AASA;;;;;;;;;GASG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAElD,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,IAAI,OAAO,CAAC;IAIpB,QAAQ,CACN,YAAY,EAAE,MAAM,WAAW,GAAG,SAAS,GAC1C,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,IAAI,SAAS;QAAE,KAAK,IAAI,SAAS,QAAQ,EAAE,CAAA;KAAE,EAAE,CAAC;CACzD,CAAC;AAEF,yEAAyE;AACzE,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAA;CAAE,CAAC;AAmCtE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,EAAE,CAAC,CAmB1B;AAED,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,QAGqC,CAAC;AAE5E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EACtC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,IAAI,CAAC,CAiBf"}
@@ -0,0 +1,108 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ /**
4
+ * Stable per-page id, so a page read across many tests keeps one identity.
5
+ *
6
+ * Weakly held: pages are closed and discarded constantly, and keeping them
7
+ * alive to remember a number would be a leak that grows with the suite.
8
+ */
9
+ const pageIds = new WeakMap();
10
+ let nextPageId = 0;
11
+ function idFor(page) {
12
+ const existing = pageIds.get(page);
13
+ if (existing !== undefined)
14
+ return existing;
15
+ const id = `page${nextPageId++}`;
16
+ pageIds.set(page, id);
17
+ return id;
18
+ }
19
+ /**
20
+ * Runs in the browser, so it must not close over anything.
21
+ *
22
+ * The assertion stands in for a type that cannot exist statically: nyc defines
23
+ * `__coverage__` at runtime, in the instrumented bundle. Declaring it as a
24
+ * global would be the alternative, but this package is published, and its
25
+ * ambient declarations would land in every consumer's type environment.
26
+ */
27
+ function readCoverageGlobal() {
28
+ return globalThis.__coverage__;
29
+ }
30
+ /**
31
+ * Every non-empty coverage map reachable in the browser, one entry per page.
32
+ *
33
+ * Only the plugin under test is instrumented, so `__coverage__` appears solely
34
+ * in pages that loaded its bundle. Which page that is depends on how the spec
35
+ * is written — some drive Playwright's `page` fixture, others open their own
36
+ * context in `beforeAll` and run every test there — so this walks every open
37
+ * context rather than assuming one.
38
+ *
39
+ * A page carrying an empty object is dropped: it is as useless as a page with
40
+ * none, and reporting it as a find would mask the "nothing was instrumented"
41
+ * case the caller reports on.
42
+ *
43
+ * Failures are handled at two levels on purpose. A single unusable page is
44
+ * skipped, because the other pages' coverage is still worth having. A failure
45
+ * reaching the browser propagates: returning an empty array would be
46
+ * indistinguishable from "nothing was instrumented", and the caller can only
47
+ * report accurately what it is allowed to see.
48
+ */
49
+ export async function collectCoverageFromBrowser(browser) {
50
+ const collected = [];
51
+ for (const context of browser.contexts()) {
52
+ for (const page of context.pages()) {
53
+ if (page.isClosed())
54
+ continue;
55
+ let coverage;
56
+ try {
57
+ coverage = await page.evaluate(readCoverageGlobal);
58
+ }
59
+ catch {
60
+ // A page can crash, navigate, or close between being listed and being
61
+ // evaluated. One unusable page must not discard the others' coverage.
62
+ continue;
63
+ }
64
+ if (coverage && Object.keys(coverage).length > 0) {
65
+ collected.push({ pageId: idFor(page), coverage });
66
+ }
67
+ }
68
+ }
69
+ return collected;
70
+ }
71
+ /** Every reason collection can legitimately come back with nothing. */
72
+ export const NO_COVERAGE_FOUND_MESSAGE = "E2E_COLLECT_COVERAGE=true but no open page exposed __coverage__. The " +
73
+ "deployed bundle may not be instrumented, the spec may have closed its " +
74
+ "pages before teardown, or evaluation may have been blocked in the page.";
75
+ /**
76
+ * Collect and persist one JSON per covered page, reporting instead of throwing.
77
+ *
78
+ * Called from fixture teardown for every test, so it must never raise: an
79
+ * unwritable output directory or a browser that died mid-test would otherwise
80
+ * fail a test that had already passed. Coverage is diagnostics, not a verdict.
81
+ * `report` is how a problem stays visible without becoming one — silence is the
82
+ * failure mode this module exists to remove.
83
+ *
84
+ * Files are named per page, not per test, and deliberately overwritten. A
85
+ * context opened in `beforeAll` outlives the whole file, and `__coverage__`
86
+ * accumulates in it, so naming per test would write a growing copy of the same
87
+ * map once per test — 30 near-duplicates of a multi-megabyte report, all summed
88
+ * again by `nyc merge`. Overwriting keeps the last and most complete read, and
89
+ * costs nothing for a per-test fixture page, which is a new page with a new id
90
+ * every time. It also collapses the retry case, where a failed and a passing
91
+ * attempt would otherwise both be counted.
92
+ */
93
+ export async function collectAndWriteCoverage(browser, target, report) {
94
+ try {
95
+ const collected = await collectCoverageFromBrowser(browser);
96
+ if (collected.length === 0) {
97
+ report(NO_COVERAGE_FOUND_MESSAGE);
98
+ return;
99
+ }
100
+ fs.mkdirSync(target.dir, { recursive: true });
101
+ for (const { pageId, coverage } of collected) {
102
+ fs.writeFileSync(path.join(target.dir, `${target.runId}-${pageId}.json`), JSON.stringify(coverage));
103
+ }
104
+ }
105
+ catch (error) {
106
+ report(`collection failed: ${error}`);
107
+ }
108
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=coverage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.test.d.ts","sourceRoot":"","sources":["../../src/playwright/coverage.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,240 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert";
3
+ import fs from "fs-extra";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import { collectAndWriteCoverage, collectCoverageFromBrowser, NO_COVERAGE_FOUND_MESSAGE, } from "./coverage.js";
7
+ /** Reports read back off disk arrive in directory order, so sort by content. */
8
+ function byFirstKey(a, b) {
9
+ return Object.keys(a)[0].localeCompare(Object.keys(b)[0]);
10
+ }
11
+ /**
12
+ * A page whose `__coverage__` is whatever `coverage` says.
13
+ *
14
+ * `evaluate` receives the real callback and would normally run it in the
15
+ * browser against that page's `globalThis`. There is no browser here, so the
16
+ * fake answers for it — which is the whole point: the behaviour under test is
17
+ * which pages get asked and what happens to the answers, not how Playwright
18
+ * ships a function across the wire.
19
+ */
20
+ function fakePage(coverage, { closed = false } = {}) {
21
+ return {
22
+ isClosed: () => closed,
23
+ evaluate: async () => coverage,
24
+ };
25
+ }
26
+ function unusablePage(reason) {
27
+ return {
28
+ isClosed: () => false,
29
+ evaluate: async () => {
30
+ throw new Error(reason);
31
+ },
32
+ };
33
+ }
34
+ /**
35
+ * The one fake that really runs the callback, against a `globalThis` carrying
36
+ * what an instrumented bundle would have left behind.
37
+ *
38
+ * Every other fake answers in place of `evaluate`, which leaves the function
39
+ * that actually names `__coverage__` unexercised — rename it and the suite
40
+ * stays green while collection silently returns nothing, one level below the
41
+ * regression this module exists to end.
42
+ */
43
+ function evaluatingPage(globals) {
44
+ return {
45
+ isClosed: () => false,
46
+ evaluate: async (pageFunction) => {
47
+ const target = globalThis;
48
+ const restore = Object.keys(globals).map((key) => [key, key in target, target[key]]);
49
+ Object.assign(target, globals);
50
+ try {
51
+ return pageFunction();
52
+ }
53
+ finally {
54
+ for (const [key, existed, previous] of restore) {
55
+ if (existed)
56
+ target[key] = previous;
57
+ else
58
+ delete target[key];
59
+ }
60
+ }
61
+ },
62
+ };
63
+ }
64
+ function fakeBrowser(...contexts) {
65
+ return { contexts: () => contexts.map((pages) => ({ pages: () => pages })) };
66
+ }
67
+ /** Istanbul keys its report by the instrumented file's absolute path. */
68
+ function coverageFor(file) {
69
+ return { [file]: { path: file } };
70
+ }
71
+ const COVERAGE = coverageFor("/src/plugin.tsx");
72
+ const OTHER_COVERAGE = coverageFor("/src/widget.tsx");
73
+ /** The coverage maps alone; page identity is asserted on its own below. */
74
+ async function mapsFrom(browser) {
75
+ const collected = await collectCoverageFromBrowser(browser);
76
+ return collected.map((entry) => entry.coverage);
77
+ }
78
+ describe("collectCoverageFromBrowser", () => {
79
+ it("collects from a context the spec opened itself", async () => {
80
+ // The regression this exists for. Specs that run every test in their own
81
+ // `browser.newContext()` reported zero coverage for months while passing,
82
+ // because collection only ever looked at the fixture page.
83
+ const fixturePage = fakePage(undefined);
84
+ const specOwnedPage = fakePage(COVERAGE);
85
+ const collected = await mapsFrom(fakeBrowser([fixturePage], [specOwnedPage]));
86
+ assert.deepStrictEqual(collected, [COVERAGE]);
87
+ });
88
+ it("returns one entry per covered context so nyc merge sums them", async () => {
89
+ const collected = await mapsFrom(fakeBrowser([fakePage(COVERAGE)], [fakePage(OTHER_COVERAGE)]));
90
+ assert.deepStrictEqual(collected, [COVERAGE, OTHER_COVERAGE], "entries stay separate; merging is nyc's job, not this function's");
91
+ });
92
+ it("collects from every page in a context, not just the first", async () => {
93
+ // One `newContext()` can hold several pages — a popup, a second tab. Taking
94
+ // `pages()[0]` would satisfy every other case in this suite.
95
+ const collected = await mapsFrom(fakeBrowser([
96
+ fakePage(undefined),
97
+ fakePage(COVERAGE),
98
+ fakePage(OTHER_COVERAGE),
99
+ ]));
100
+ assert.deepStrictEqual(collected, [COVERAGE, OTHER_COVERAGE], "every page of a context is probed, in page order");
101
+ });
102
+ it("moves past a context holding no pages", async () => {
103
+ const collected = await mapsFrom(fakeBrowser([], [fakePage(COVERAGE)]));
104
+ assert.deepStrictEqual(collected, [COVERAGE]);
105
+ });
106
+ it("reads __coverage__ off the page's own globalThis", async () => {
107
+ const collected = await mapsFrom(
108
+ // eslint-disable-next-line @typescript-eslint/naming-convention
109
+ fakeBrowser([evaluatingPage({ __coverage__: COVERAGE })]));
110
+ assert.deepStrictEqual(collected, [COVERAGE], "the global nyc writes is the global this reads");
111
+ });
112
+ it("finds nothing in a page that loaded no instrumented bundle", async () => {
113
+ const collected = await mapsFrom(fakeBrowser([evaluatingPage({})]));
114
+ assert.deepStrictEqual(collected, []);
115
+ });
116
+ it("keeps other pages' coverage when one page cannot be evaluated", async () => {
117
+ const collected = await mapsFrom(fakeBrowser([unusablePage("Target page, context or browser has been closed")], [fakePage(COVERAGE)]));
118
+ assert.deepStrictEqual(collected, [COVERAGE], "a single crashed page used to discard the whole test's coverage");
119
+ });
120
+ it("skips closed pages without evaluating them", async () => {
121
+ let evaluated = false;
122
+ const closedPage = {
123
+ isClosed: () => true,
124
+ evaluate: async () => {
125
+ evaluated = true;
126
+ return COVERAGE;
127
+ },
128
+ };
129
+ const collected = await mapsFrom(fakeBrowser([closedPage]));
130
+ assert.deepStrictEqual(collected, []);
131
+ assert.strictEqual(evaluated, false, "a closed page must not be probed");
132
+ });
133
+ it("ignores a page with no coverage", async () => {
134
+ const collected = await mapsFrom(fakeBrowser([fakePage(undefined)]));
135
+ assert.deepStrictEqual(collected, []);
136
+ });
137
+ it("ignores an empty coverage object", async () => {
138
+ // An instrumented bundle that never executed leaves `{}` behind. Treating
139
+ // it as a find would write an empty JSON and suppress the warning that is
140
+ // supposed to flag exactly this.
141
+ const collected = await mapsFrom(fakeBrowser([fakePage({})]));
142
+ assert.deepStrictEqual(collected, []);
143
+ });
144
+ it("returns nothing when no context is open", async () => {
145
+ assert.deepStrictEqual(await mapsFrom(fakeBrowser()), []);
146
+ });
147
+ it("propagates a browser-level failure instead of reporting no coverage", async () => {
148
+ // The caller turns this into a diagnostic that names the real cause.
149
+ // Swallowing it here would return an empty array indistinguishable from
150
+ // "nothing was instrumented" — the confusion this module exists to end.
151
+ const deadBrowser = {
152
+ contexts: () => {
153
+ throw new Error("Browser has been closed");
154
+ },
155
+ };
156
+ await assert.rejects(() => collectCoverageFromBrowser(deadBrowser), /Browser has been closed/);
157
+ });
158
+ it("gives one page the same id across collections", async () => {
159
+ // What makes the per-page filename overwrite instead of accumulate: a
160
+ // context opened in `beforeAll` is read again on every test, and each read
161
+ // returns the same growing map.
162
+ const longLived = fakePage(COVERAGE);
163
+ const browser = fakeBrowser([longLived]);
164
+ const first = await collectCoverageFromBrowser(browser);
165
+ const second = await collectCoverageFromBrowser(browser);
166
+ assert.strictEqual(first[0].pageId, second[0].pageId);
167
+ });
168
+ it("gives distinct pages distinct ids", async () => {
169
+ const collected = await collectCoverageFromBrowser(fakeBrowser([fakePage(COVERAGE)], [fakePage(OTHER_COVERAGE)]));
170
+ assert.notStrictEqual(collected[0].pageId, collected[1].pageId, "sharing an id would make one page's file overwrite another's");
171
+ });
172
+ });
173
+ describe("collectAndWriteCoverage", () => {
174
+ async function inTempDir(run) {
175
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "coverage-test-"));
176
+ try {
177
+ await run(dir);
178
+ }
179
+ finally {
180
+ await fs.remove(dir);
181
+ }
182
+ }
183
+ it("writes one file per page, both readable back", async () => {
184
+ await inTempDir(async (dir) => {
185
+ const reported = [];
186
+ await collectAndWriteCoverage(fakeBrowser([fakePage(COVERAGE)], [fakePage(OTHER_COVERAGE)]), { dir, runId: "w0" }, (detail) => reported.push(detail));
187
+ const files = (await fs.readdir(dir)).sort();
188
+ assert.strictEqual(files.length, 2, "two pages must not collide");
189
+ const written = await Promise.all(files.map((file) => fs.readJson(path.join(dir, file))));
190
+ assert.deepStrictEqual(written.sort(byFirstKey), [
191
+ COVERAGE,
192
+ OTHER_COVERAGE,
193
+ ]);
194
+ assert.deepStrictEqual(reported, [], "a clean run reports nothing");
195
+ });
196
+ });
197
+ it("overwrites rather than accumulating when a page is read again", async () => {
198
+ // The long-lived-context case: 34 tests must not leave 34 copies of the
199
+ // same growing map for `nyc merge` to sum.
200
+ await inTempDir(async (dir) => {
201
+ const browser = fakeBrowser([fakePage(COVERAGE)]);
202
+ await collectAndWriteCoverage(browser, { dir, runId: "w0" }, () => { });
203
+ await collectAndWriteCoverage(browser, { dir, runId: "w0" }, () => { });
204
+ assert.strictEqual((await fs.readdir(dir)).length, 1);
205
+ });
206
+ });
207
+ it("reports instead of throwing when the browser is gone", async () => {
208
+ await inTempDir(async (dir) => {
209
+ const reported = [];
210
+ const deadBrowser = {
211
+ contexts: () => {
212
+ throw new Error("Browser has been closed");
213
+ },
214
+ };
215
+ await collectAndWriteCoverage(deadBrowser, { dir, runId: "w0" }, (detail) => reported.push(detail));
216
+ assert.strictEqual(reported.length, 1);
217
+ assert.match(reported[0], /Browser has been closed/, "the report must name the real cause, not just 'no coverage'");
218
+ });
219
+ });
220
+ it("reports instead of throwing when the directory cannot be created", async () => {
221
+ // Teardown runs for every test, so an unwritable outputDir must not be able
222
+ // to fail a test that already passed.
223
+ await inTempDir(async (dir) => {
224
+ const blocked = path.join(dir, "a-file-not-a-dir");
225
+ await fs.writeFile(blocked, "");
226
+ const reported = [];
227
+ await collectAndWriteCoverage(fakeBrowser([fakePage(COVERAGE)]), { dir: blocked, runId: "w0" }, (detail) => reported.push(detail));
228
+ assert.strictEqual(reported.length, 1);
229
+ assert.match(reported[0], /collection failed/);
230
+ });
231
+ });
232
+ it("reports the no-coverage diagnosis when nothing is instrumented", async () => {
233
+ await inTempDir(async (dir) => {
234
+ const reported = [];
235
+ await collectAndWriteCoverage(fakeBrowser([fakePage(undefined)]), { dir, runId: "w0" }, (detail) => reported.push(detail));
236
+ assert.deepStrictEqual(reported, [NO_COVERAGE_FOUND_MESSAGE]);
237
+ assert.strictEqual(await fs.pathExists(dir), true, "reporting must not depend on having written anything");
238
+ });
239
+ });
240
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../../src/playwright/fixtures/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAMzC,KAAK,0BAA0B,GAAG;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,IAAI,CAAC;IAEtB,kBAAkB,EAAE,IAAI,CAAC;CAC1B,CAAC;AAEF,KAAK,4BAA4B,GAAG;IAClC,oBAAoB,EAAE,cAAc,CAAC;CACtC,CAAC;AA4FF,eAAO,MAAM,IAAI;;CAEf,CAAC;AAEH,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../../src/playwright/fixtures/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAuBzC,KAAK,0BAA0B,GAAG;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,IAAI,CAAC;IAEtB,kBAAkB,EAAE,IAAI,CAAC;CAC1B,CAAC;AAEF,KAAK,4BAA4B,GAAG;IAClC,oBAAoB,EAAE,cAAc,CAAC;CACtC,CAAC;AA4FF,eAAO,MAAM,IAAI;;CAEf,CAAC;AAEH,cAAc,kBAAkB,CAAC"}
@@ -4,8 +4,25 @@ import { LoginHelper, UIhelper } from "../helpers/index.js";
4
4
  import { runOnce } from "../run-once.js";
5
5
  import { $ } from "../../utils/bash.js";
6
6
  import { WorkspacePaths } from "../../utils/workspace-paths.js";
7
- import fs from "node:fs";
7
+ import { collectAndWriteCoverage } from "../coverage.js";
8
+ import { isCoverageEnabled } from "../../utils/common.js";
8
9
  import path from "path";
10
+ // Asking for coverage and getting none used to look exactly like success: the
11
+ // collector returned quietly and the run stayed green while every JSON was
12
+ // lost. Reporting it is deliberately two-channel — an annotation lands in the
13
+ // HTML report beside the result, the way `autoAnnotations` below does, and a
14
+ // console line keeps it in the CI log, where a green run is still read.
15
+ //
16
+ // Deduplicated by message rather than by a single flag: one recurring
17
+ // "no coverage found" must not be what silences the first real exception.
18
+ const loggedCoverageProblems = new Set();
19
+ function reportCoverageProblem(testInfo, detail) {
20
+ testInfo.annotations.push({ type: "coverage", description: detail });
21
+ if (loggedCoverageProblems.has(detail))
22
+ return;
23
+ loggedCoverageProblems.add(detail);
24
+ console.warn(`[coverage] ${detail}`);
25
+ }
9
26
  const baseTest = base.extend({
10
27
  rhdhDeploymentWorker: [
11
28
  // eslint-disable-next-line no-empty-pattern
@@ -59,21 +76,25 @@ const baseTest = base.extend({
59
76
  ],
60
77
  // eslint-disable-next-line @typescript-eslint/naming-convention
61
78
  _coverageCollector: [
62
- async ({ page }, use, testInfo) => {
79
+ // Reads through `browser` so a context the spec opened itself is seen too,
80
+ // but must also depend on `context`. Playwright sets auto fixtures up
81
+ // before the ones a test asks for and tears them down in reverse, so
82
+ // depending on `browser` alone puts this after the context fixture has
83
+ // already closed — `browser.contexts()` comes back empty and every spec
84
+ // driving the plain `page` fixture reports nothing. Naming `context` makes
85
+ // this a dependent of it, which is what orders teardown correctly. It is
86
+ // still lighter than depending on `page`: a context is created, not a page.
87
+ async ({ browser, context }, use, testInfo) => {
88
+ void context; // Depended on for teardown ordering, not for its value.
63
89
  await use();
64
- if (process.env.E2E_COLLECT_COVERAGE !== "true")
90
+ if (!isCoverageEnabled())
65
91
  return;
66
- try {
67
- const coverage = await page.evaluate(() => globalThis.__coverage__);
68
- if (!coverage)
69
- return;
70
- const dir = path.join(testInfo.project.outputDir, "coverage");
71
- fs.mkdirSync(dir, { recursive: true });
72
- fs.writeFileSync(path.join(dir, `${testInfo.testId}-${Date.now()}.json`), JSON.stringify(coverage));
73
- }
74
- catch {
75
- // Best-effort: page may have crashed or been closed
76
- }
92
+ await collectAndWriteCoverage(browser, {
93
+ dir: path.join(testInfo.project.outputDir, "coverage"),
94
+ // Unique per worker process, so two workers writing the same
95
+ // project's outputDir cannot land on the same filename.
96
+ runId: `w${testInfo.workerIndex}`,
97
+ }, (detail) => reportCoverageProblem(testInfo, detail));
77
98
  },
78
99
  { auto: true, scope: "test" },
79
100
  ],
@@ -1,4 +1,12 @@
1
1
  declare function envsubst(str: string, env?: NodeJS.ProcessEnv): string;
2
2
  declare function requireEnv(...varNames: [string, ...string[]]): void;
3
- export { envsubst, requireEnv };
3
+ /**
4
+ * Whether this run should deploy instrumented bundles and collect coverage.
5
+ *
6
+ * Read from two layers that have to agree — plugin resolution picks the
7
+ * `__coverage` OCI tag, the Playwright fixture reads `__coverage__` back out —
8
+ * so the check lives in one place rather than as a string compare in each.
9
+ */
10
+ declare function isCoverageEnabled(): boolean;
11
+ export { envsubst, requireEnv, isCoverageEnabled };
4
12
  //# sourceMappingURL=common.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/utils/common.ts"],"names":[],"mappings":"AAAA,iBAAS,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,oBAAc,UAS/C;AAED,iBAAS,UAAU,CAAC,GAAG,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAO5D;AAED,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/utils/common.ts"],"names":[],"mappings":"AAAA,iBAAS,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,oBAAc,UAS/C;AAED,iBAAS,UAAU,CAAC,GAAG,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,GAAG,IAAI,CAO5D;AAED;;;;;;GAMG;AACH,iBAAS,iBAAiB,IAAI,OAAO,CAEpC;AAED,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC"}
@@ -13,4 +13,14 @@ function requireEnv(...varNames) {
13
13
  }
14
14
  }
15
15
  }
16
- export { envsubst, requireEnv };
16
+ /**
17
+ * Whether this run should deploy instrumented bundles and collect coverage.
18
+ *
19
+ * Read from two layers that have to agree — plugin resolution picks the
20
+ * `__coverage` OCI tag, the Playwright fixture reads `__coverage__` back out —
21
+ * so the check lives in one place rather than as a string compare in each.
22
+ */
23
+ function isCoverageEnabled() {
24
+ return process.env.E2E_COLLECT_COVERAGE === "true";
25
+ }
26
+ export { envsubst, requireEnv, isCoverageEnabled };
@@ -44,7 +44,7 @@ export declare function fetchDefaultPackages(): Promise<Set<string>>;
44
44
  * Resolution priority:
45
45
  * 1. NIGHTLY_DPDY_OCI_REGISTRY_MAP — JSON object mapping registry → array of package names
46
46
  * 2. NIGHTLY_DPDY_OCI_REGISTRY — blanket override for all plugins using {{inherit}}
47
- * 3. Default: registry.access.redhat.com/rhdh
47
+ * 3. Default: quay.io/rhdh
48
48
  */
49
49
  export declare function getDpdyRegistry(packageName: string): string;
50
50
  /**
@@ -101,7 +101,7 @@ export declare function normalizeDisablePluginName(plugin: string): string;
101
101
  * mountPoints / config conflicts with the workspace's PR OCI image.
102
102
  *
103
103
  * Registry resolution: NIGHTLY_DPDY_OCI_REGISTRY, else
104
- * registry.access.redhat.com/rhdh.
104
+ * quay.io/rhdh.
105
105
  *
106
106
  * @param plugins plugin names, wrapper paths, and/or OCI refs to disable
107
107
  * @returns Dynamic plugins configuration that disables listed plugins
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/plugin-metadata.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAgBD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAID;;;;;;;;;GASG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAqBtC;AAoBD;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CA+CjE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAc3D;AAID;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI5D;AAYD,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAEnD,wBAAgB,oBAAoB,CAClC,YAAY,GAAE,MAA8B,GAC3C,MAAM,GAAG,IAAI,CAQf;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CA2BzB;AAED,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAwBtC;AA2JD;;;;;;;GAOG;AACH;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAMT;AA0HD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAajE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,oBAAoB,CAuBtE;AAED;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAC/C,YAAY,GAAE,MAA8B,GAC3C,OAAO,CAAC,oBAAoB,CAAC,CAwB/B;AA2BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,oBAAoB,EAC5B,YAAY,GAAE,MAA8B,EAC5C,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAsC/B"}
1
+ {"version":3,"file":"plugin-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/plugin-metadata.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAgBD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAID;;;;;;;;;GASG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAqBtC;AAoBD;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CA+CjE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAc3D;AAID;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI5D;AAYD,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAEnD,wBAAgB,oBAAoB,CAClC,YAAY,GAAE,MAA8B,GAC3C,MAAM,GAAG,IAAI,CAQf;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CA2BzB;AAED,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAwBtC;AA2JD;;;;;;;GAOG;AACH;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAMT;AAyHD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAajE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,oBAAoB,CAuBtE;AAED;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAC/C,YAAY,GAAE,MAA8B,GAC3C,OAAO,CAAC,oBAAoB,CAAC,CAwB/B;AA2BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,oBAAoB,EAC5B,YAAY,GAAE,MAA8B,EAC5C,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAsC/B"}
@@ -3,6 +3,7 @@ import path from "path";
3
3
  import yaml from "js-yaml";
4
4
  import { glob } from "zx";
5
5
  import { deepMerge } from "./merge-yamls.js";
6
+ import { isCoverageEnabled } from "./common.js";
6
7
  const OCI_REGISTRY_PREFIX = "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays";
7
8
  // ── Detection ─────────────────────────────────────────────────────────────────
8
9
  /**
@@ -36,7 +37,7 @@ export function isNightlyJob() {
36
37
  const DEFAULT_PACKAGES_BASE_URL = "https://raw.githubusercontent.com/redhat-developer/rhdh-plugin-export-overlays/refs/heads";
37
38
  // release-1.10 still hosts default.packages.yaml in the rhdh repo (pre-migration)
38
39
  const LEGACY_DEFAULT_PACKAGES_BASE_URL = "https://raw.githubusercontent.com/redhat-developer/rhdh/refs/heads";
39
- const DEFAULT_DPDY_OCI_REGISTRY = "registry.access.redhat.com/rhdh";
40
+ const DEFAULT_DPDY_OCI_REGISTRY = "quay.io/rhdh";
40
41
  /**
41
42
  * Fetches the list of packages from default.packages.yaml (source for the
42
43
  * dynamic-plugins.default.yaml — DPDY — in the catalog index image shipped
@@ -85,7 +86,7 @@ export async function fetchDefaultPackages() {
85
86
  * Resolution priority:
86
87
  * 1. NIGHTLY_DPDY_OCI_REGISTRY_MAP — JSON object mapping registry → array of package names
87
88
  * 2. NIGHTLY_DPDY_OCI_REGISTRY — blanket override for all plugins using {{inherit}}
88
- * 3. Default: registry.access.redhat.com/rhdh
89
+ * 3. Default: quay.io/rhdh
89
90
  */
90
91
  export function getDpdyRegistry(packageName) {
91
92
  const map = process.env.NIGHTLY_DPDY_OCI_REGISTRY_MAP;
@@ -302,8 +303,7 @@ async function resolvePluginPackages(plugins, metadataMap, metadataPath, dpdyPac
302
303
  if (prOciUrls) {
303
304
  const prUrl = prOciUrls.get(displayName);
304
305
  if (prUrl) {
305
- const usesCoverage = process.env.E2E_COLLECT_COVERAGE === "true" &&
306
- metadata.role === "frontend-plugin";
306
+ const usesCoverage = isCoverageEnabled() && metadata.role === "frontend-plugin";
307
307
  const resolved = usesCoverage
308
308
  ? prUrl.replace(/(:[^!]+)/, "$1__coverage")
309
309
  : prUrl;
@@ -398,7 +398,7 @@ export function normalizeDisablePluginName(plugin) {
398
398
  * mountPoints / config conflicts with the workspace's PR OCI image.
399
399
  *
400
400
  * Registry resolution: NIGHTLY_DPDY_OCI_REGISTRY, else
401
- * registry.access.redhat.com/rhdh.
401
+ * quay.io/rhdh.
402
402
  *
403
403
  * @param plugins plugin names, wrapper paths, and/or OCI refs to disable
404
404
  * @returns Dynamic plugins configuration that disables listed plugins
@@ -234,7 +234,7 @@ describe("processPluginsForDeployment — nightly {{inherit}}", () => {
234
234
  };
235
235
  const dpdyPackages = new Set(["@backstage-community/plugin-tekton"]);
236
236
  const result = await processPluginsForDeployment(config, metadataDir, dpdyPackages);
237
- assert.strictEqual(result.plugins[0].package, "oci://registry.access.redhat.com/rhdh/backstage-community-plugin-tekton:{{inherit}}", "DPDY OCI plugin must resolve to {{inherit}} with default RHEC registry");
237
+ assert.strictEqual(result.plugins[0].package, "oci://quay.io/rhdh/backstage-community-plugin-tekton:{{inherit}}", "DPDY OCI plugin must resolve to {{inherit}} with default quay.io registry");
238
238
  }
239
239
  finally {
240
240
  await fs.remove(metadataDir);
@@ -258,7 +258,7 @@ describe("processPluginsForDeployment — nightly {{inherit}}", () => {
258
258
  ],
259
259
  };
260
260
  const result = await processPluginsForDeployment(config, metadataDir, new Set(["@backstage-community/plugin-topology"]));
261
- assert.strictEqual(result.plugins[0].package, "oci://registry.access.redhat.com/rhdh/backstage-community-plugin-topology:{{inherit}}", "{{inherit}} ref must use default RHEC registry with no alias suffix");
261
+ assert.strictEqual(result.plugins[0].package, "oci://quay.io/rhdh/backstage-community-plugin-topology:{{inherit}}", "{{inherit}} ref must use default RHEC registry with no alias suffix");
262
262
  assert.ok(!result.plugins[0].package.includes("!"), "{{inherit}} ref must NOT include !alias suffix");
263
263
  }
264
264
  finally {
@@ -283,7 +283,7 @@ describe("processPluginsForDeployment — nightly {{inherit}}", () => {
283
283
  ],
284
284
  };
285
285
  const result = await processPluginsForDeployment(config, metadataDir, new Set(["@red-hat-developer-hub/backstage-plugin-orchestrator"]));
286
- assert.strictEqual(result.plugins[0].package, "oci://registry.access.redhat.com/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator:{{inherit}}", "{{inherit}} must use default RHEC registry regardless of metadata's ghcr.io");
286
+ assert.strictEqual(result.plugins[0].package, "oci://quay.io/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator:{{inherit}}", "{{inherit}} must use default RHEC registry regardless of metadata's ghcr.io");
287
287
  }
288
288
  finally {
289
289
  await fs.remove(metadataDir);
@@ -296,14 +296,14 @@ describe("processPluginsForDeployment — nightly {{inherit}}", () => {
296
296
  {
297
297
  name: "backstage-community-plugin-tekton",
298
298
  packageName: "@backstage-community/plugin-tekton",
299
- dynamicArtifact: "oci://registry.access.redhat.com/rhdh/backstage-community-plugin-tekton@sha256:abc",
299
+ dynamicArtifact: "oci://quay.io/rhdh/backstage-community-plugin-tekton@sha256:abc",
300
300
  },
301
301
  ]);
302
302
  try {
303
303
  const config = {
304
304
  plugins: [
305
305
  {
306
- package: "oci://registry.access.redhat.com/rhdh/backstage-community-plugin-tekton@sha256:abc",
306
+ package: "oci://quay.io/rhdh/backstage-community-plugin-tekton@sha256:abc",
307
307
  disabled: false,
308
308
  },
309
309
  ],
@@ -325,7 +325,7 @@ describe("processPluginsForDeployment — nightly {{inherit}}", () => {
325
325
  {
326
326
  name: "backstage-community-plugin-tekton",
327
327
  packageName: "@backstage-community/plugin-tekton",
328
- dynamicArtifact: "oci://registry.access.redhat.com/rhdh/backstage-community-plugin-tekton@sha256:abc",
328
+ dynamicArtifact: "oci://quay.io/rhdh/backstage-community-plugin-tekton@sha256:abc",
329
329
  },
330
330
  {
331
331
  name: "red-hat-developer-hub-backstage-plugin-orchestrator",
@@ -337,7 +337,7 @@ describe("processPluginsForDeployment — nightly {{inherit}}", () => {
337
337
  const config = {
338
338
  plugins: [
339
339
  {
340
- package: "oci://registry.access.redhat.com/rhdh/backstage-community-plugin-tekton@sha256:abc",
340
+ package: "oci://quay.io/rhdh/backstage-community-plugin-tekton@sha256:abc",
341
341
  disabled: false,
342
342
  },
343
343
  {
@@ -352,7 +352,7 @@ describe("processPluginsForDeployment — nightly {{inherit}}", () => {
352
352
  ]);
353
353
  const result = await processPluginsForDeployment(config, metadataDir, dpdyPackages);
354
354
  assert.strictEqual(result.plugins[0].package, "oci://ghcr.io/redhat-developer/rhdh-plugin-export-overlays/backstage-community-plugin-tekton:{{inherit}}", "tekton must use ghcr.io from NIGHTLY_DPDY_OCI_REGISTRY_MAP");
355
- assert.strictEqual(result.plugins[1].package, "oci://registry.access.redhat.com/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator:{{inherit}}", "orchestrator must use default RHEC (not in map)");
355
+ assert.strictEqual(result.plugins[1].package, "oci://quay.io/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator:{{inherit}}", "orchestrator must use default RHEC (not in map)");
356
356
  }
357
357
  finally {
358
358
  await fs.remove(metadataDir);
@@ -369,23 +369,23 @@ describe("processPluginsForDeployment — nightly {{inherit}}", () => {
369
369
  {
370
370
  name: "backstage-community-plugin-tekton",
371
371
  packageName: "@backstage-community/plugin-tekton",
372
- dynamicArtifact: "oci://registry.access.redhat.com/rhdh/backstage-community-plugin-tekton@sha256:abc",
372
+ dynamicArtifact: "oci://quay.io/rhdh/backstage-community-plugin-tekton@sha256:abc",
373
373
  },
374
374
  {
375
375
  name: "red-hat-developer-hub-backstage-plugin-orchestrator",
376
376
  packageName: "@red-hat-developer-hub/backstage-plugin-orchestrator",
377
- dynamicArtifact: "oci://registry.access.redhat.com/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator@sha256:def",
377
+ dynamicArtifact: "oci://quay.io/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator@sha256:def",
378
378
  },
379
379
  ]);
380
380
  try {
381
381
  const config = {
382
382
  plugins: [
383
383
  {
384
- package: "oci://registry.access.redhat.com/rhdh/backstage-community-plugin-tekton@sha256:abc",
384
+ package: "oci://quay.io/rhdh/backstage-community-plugin-tekton@sha256:abc",
385
385
  disabled: false,
386
386
  },
387
387
  {
388
- package: "oci://registry.access.redhat.com/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator@sha256:def",
388
+ package: "oci://quay.io/rhdh/red-hat-developer-hub-backstage-plugin-orchestrator@sha256:def",
389
389
  disabled: false,
390
390
  },
391
391
  ],
@@ -547,7 +547,7 @@ describe("processPluginsForDeployment — nightly {{inherit}}", () => {
547
547
  // Only tekton is in DPDY
548
548
  const result = await processPluginsForDeployment(config, metadataDir, new Set(["@backstage-community/plugin-tekton"]));
549
549
  // Tekton: DPDY → {{inherit}} with default RHEC, no config injection
550
- assert.strictEqual(result.plugins[0].package, "oci://registry.access.redhat.com/rhdh/backstage-community-plugin-tekton:{{inherit}}", "DPDY plugin must use {{inherit}} with default RHEC registry");
550
+ assert.strictEqual(result.plugins[0].package, "oci://quay.io/rhdh/backstage-community-plugin-tekton:{{inherit}}", "DPDY plugin must use {{inherit}} with default RHEC registry");
551
551
  assert.strictEqual(result.plugins[0].pluginConfig, undefined, "DPDY plugin must not have config injected");
552
552
  // Scorecard: non-DPDY → full OCI ref, config injected
553
553
  assert.ok(result.plugins[1].package.includes("bs_1.49.4__1.0.0"), "non-DPDY plugin must use full metadata ref");
@@ -566,7 +566,7 @@ describe("getDpdyRegistry", () => {
566
566
  it("returns default RHEC registry when no env vars set", () => {
567
567
  delete process.env.NIGHTLY_DPDY_OCI_REGISTRY;
568
568
  delete process.env.NIGHTLY_DPDY_OCI_REGISTRY_MAP;
569
- assert.strictEqual(getDpdyRegistry("@backstage-community/plugin-tekton"), "registry.access.redhat.com/rhdh");
569
+ assert.strictEqual(getDpdyRegistry("@backstage-community/plugin-tekton"), "quay.io/rhdh");
570
570
  });
571
571
  it("NIGHTLY_DPDY_OCI_REGISTRY overrides default for all plugins", () => {
572
572
  process.env.NIGHTLY_DPDY_OCI_REGISTRY =
@@ -593,7 +593,7 @@ describe("getDpdyRegistry", () => {
593
593
  "@backstage-community/plugin-tekton",
594
594
  ],
595
595
  });
596
- assert.strictEqual(getDpdyRegistry("@red-hat-developer-hub/backstage-plugin-orchestrator"), "registry.access.redhat.com/rhdh", "unlisted plugin must fall back to default RHEC");
596
+ assert.strictEqual(getDpdyRegistry("@red-hat-developer-hub/backstage-plugin-orchestrator"), "quay.io/rhdh", "unlisted plugin must fall back to default RHEC");
597
597
  });
598
598
  it("NIGHTLY_DPDY_OCI_REGISTRY_MAP takes precedence over NIGHTLY_DPDY_OCI_REGISTRY", () => {
599
599
  process.env.NIGHTLY_DPDY_OCI_REGISTRY = "quay.io/rhdh";
@@ -609,10 +609,12 @@ describe("getDpdyRegistry", () => {
609
609
  "ghcr.io/redhat-developer/rhdh-plugin-export-overlays": [
610
610
  "@backstage-community/plugin-tekton",
611
611
  ],
612
- "quay.io/rhdh": ["@red-hat-developer-hub/backstage-plugin-orchestrator"],
612
+ "registry.access.redhat.com": [
613
+ "@red-hat-developer-hub/backstage-plugin-orchestrator",
614
+ ],
613
615
  });
614
616
  assert.strictEqual(getDpdyRegistry("@backstage-community/plugin-tekton"), "ghcr.io/redhat-developer/rhdh-plugin-export-overlays");
615
- assert.strictEqual(getDpdyRegistry("@red-hat-developer-hub/backstage-plugin-orchestrator"), "quay.io/rhdh");
616
- assert.strictEqual(getDpdyRegistry("@backstage-community/plugin-argocd"), "registry.access.redhat.com/rhdh", "unlisted plugin falls back to default");
617
+ assert.strictEqual(getDpdyRegistry("@red-hat-developer-hub/backstage-plugin-orchestrator"), "registry.access.redhat.com");
618
+ assert.strictEqual(getDpdyRegistry("@backstage-community/plugin-argocd"), "quay.io/rhdh", "unlisted plugin falls back to default");
617
619
  });
618
620
  });
@@ -86,7 +86,7 @@ describe("disablePlugins", () => {
86
86
  disabled: true,
87
87
  });
88
88
  assert.deepStrictEqual(result.plugins[1], {
89
- package: "oci://registry.access.redhat.com/rhdh/backstage-community-plugin-tech-radar:{{inherit}}",
89
+ package: "oci://quay.io/rhdh/backstage-community-plugin-tech-radar:{{inherit}}",
90
90
  disabled: true,
91
91
  });
92
92
  assert.deepStrictEqual(result.plugins[2], {
@@ -94,7 +94,7 @@ describe("disablePlugins", () => {
94
94
  disabled: true,
95
95
  });
96
96
  assert.deepStrictEqual(result.plugins[3], {
97
- package: "oci://registry.access.redhat.com/rhdh/backstage-plugin-kubernetes:{{inherit}}",
97
+ package: "oci://quay.io/rhdh/backstage-plugin-kubernetes:{{inherit}}",
98
98
  disabled: true,
99
99
  });
100
100
  });
@@ -103,7 +103,7 @@ describe("disablePlugins", () => {
103
103
  const result = disablePlugins([
104
104
  "./dynamic-plugins/dist/backstage-plugin-kubernetes-dynamic",
105
105
  "backstage-plugin-kubernetes",
106
- "oci://registry.access.redhat.com/rhdh/backstage-plugin-kubernetes:{{inherit}}",
106
+ "oci://quay.io/rhdh/backstage-plugin-kubernetes:{{inherit}}",
107
107
  ]);
108
108
  assert.strictEqual(result.plugins.length, 2);
109
109
  assert.deepStrictEqual(result.plugins[0], {
@@ -111,7 +111,7 @@ describe("disablePlugins", () => {
111
111
  disabled: true,
112
112
  });
113
113
  assert.deepStrictEqual(result.plugins[1], {
114
- package: "oci://registry.access.redhat.com/rhdh/backstage-plugin-kubernetes:{{inherit}}",
114
+ package: "oci://quay.io/rhdh/backstage-plugin-kubernetes:{{inherit}}",
115
115
  disabled: true,
116
116
  });
117
117
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@red-hat-developer-hub/e2e-test-utils",
3
- "version": "2.1.7",
3
+ "version": "2.1.9",
4
4
  "description": "Test utilities for RHDH E2E tests",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {