diff --git a/package-lock.json b/package-lock.json index 2e795bd..4494bc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@qawolf/pom", - "version": "0.6.1", + "version": "0.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@qawolf/pom", - "version": "0.6.1", + "version": "0.6.2", "license": "MIT", "devDependencies": { "@qawolf/flows": "^0.1.4", diff --git a/package.json b/package.json index 2e211ae..1699e9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@qawolf/pom", - "version": "0.6.1", + "version": "0.6.2", "private": false, "license": "MIT", "repository": { diff --git a/src/entryPointPageObject.test.ts b/src/entryPointPageObject.test.ts index 1b4534c..357bcd6 100644 --- a/src/entryPointPageObject.test.ts +++ b/src/entryPointPageObject.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, jest } from "@jest/globals"; +import { afterEach, describe, expect, it, jest } from "@jest/globals"; import type { Browser, BrowserContext, @@ -270,6 +270,70 @@ describe("[CI] EntryPointPageObject browser launch", () => { }); }); +describe("[CI] initializeBrowser — extra HTTP header from env", () => { + const originalEnv = { ...process.env }; + + afterEach(() => { + process.env = { ...originalEnv }; + }); + + it("sends nothing extra when only one of the two env vars is set", async () => { + makeFakeContext(); + delete process.env.EXTRA_HTTP_HEADER_NAME; + process.env.EXTRA_HTTP_HEADER_VALUE = "some-token"; + + await PlainEntry.create(); + + const launched = launch.mock.calls[0]?.[0] as + { extraHTTPHeaders?: Record } | undefined; + expect(launched?.extraHTTPHeaders).toBeUndefined(); + }); + + it("adds the header named by EXTRA_HTTP_HEADER_NAME when both are set", async () => { + makeFakeContext(); + process.env.EXTRA_HTTP_HEADER_NAME = "X-Gym-Auth"; + process.env.EXTRA_HTTP_HEADER_VALUE = "dGVzdGVyOnMzY3JldA=="; + + await PlainEntry.create(); + + expect(launch).toHaveBeenCalledWith( + expect.objectContaining({ + extraHTTPHeaders: { "X-Gym-Auth": "dGVzdGVyOnMzY3JldA==" }, + }), + ); + }); + + it("merges with, rather than replaces, a flow's own extraHTTPHeaders", async () => { + makeFakeContext(); + process.env.EXTRA_HTTP_HEADER_NAME = "X-Gym-Auth"; + process.env.EXTRA_HTTP_HEADER_VALUE = "token"; + + await PlainEntry.create({ extraHTTPHeaders: { "app-shell": "true" } }); + + expect(launch).toHaveBeenCalledWith( + expect.objectContaining({ + extraHTTPHeaders: { "app-shell": "true", "X-Gym-Auth": "token" }, + }), + ); + }); + + it("lets the flow's own header of the same name win", async () => { + makeFakeContext(); + process.env.EXTRA_HTTP_HEADER_NAME = "X-Gym-Auth"; + process.env.EXTRA_HTTP_HEADER_VALUE = "env-token"; + + await PlainEntry.create({ + extraHTTPHeaders: { "X-Gym-Auth": "flow-token" }, + }); + + expect(launch).toHaveBeenCalledWith( + expect.objectContaining({ + extraHTTPHeaders: { "X-Gym-Auth": "flow-token" }, + }), + ); + }); +}); + describe("[CI] initializeBrowser — declared hooks", () => { it("shields cssSelector popups with one init script on the context", async () => { const { initScripts, pages } = makeFakeContext(); diff --git a/src/entryPointPageObject.ts b/src/entryPointPageObject.ts index b48405f..08544ad 100644 --- a/src/entryPointPageObject.ts +++ b/src/entryPointPageObject.ts @@ -27,6 +27,22 @@ function optionalHttpCredentialsFromEnv(): return {}; } +/** + * A single extra request header from the environment, for sites gated by a + * custom auth header instead of HTTP Basic auth (which occupies + * `Authorization` — not an option when the site under test needs that + * header for its own auth). Only applied when both `EXTRA_HTTP_HEADER_NAME` + * and `EXTRA_HTTP_HEADER_VALUE` are non-empty, for the same reason empty + * `AUTH_USERNAME`/`AUTH_PASSWORD` above are skipped rather than sent empty. + */ +function optionalExtraHttpHeaderFromEnv(): Record | undefined { + const name = process.env.EXTRA_HTTP_HEADER_NAME; + const value = process.env.EXTRA_HTTP_HEADER_VALUE; + if (name && value) return { [name]: value }; + + return undefined; +} + /** * Names are unique within one source so that `allowPopups` / `allowRoutes` * and same-name replacement are unambiguous. Across sources a repeat is @@ -163,9 +179,18 @@ export abstract class EntryPointPageObject extends BasePageObject { kind: "route", }); + const extraHttpHeaderFromEnv = optionalExtraHttpHeaderFromEnv(); const launchResult = await launch({ ...launchOptions, ...optionalHttpCredentialsFromEnv(), + ...(extraHttpHeaderFromEnv + ? { + extraHTTPHeaders: { + ...extraHttpHeaderFromEnv, + ...launchOptions.extraHTTPHeaders, + }, + } + : {}), }); if (!("browser" in launchResult))