Skip to content
79 changes: 73 additions & 6 deletions ws-nextjs-app/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,76 @@ Cypress.Commands.add('getToggles', getToggles);
Cypress.Commands.add('hasNoscriptImgAtiUrl', hasNoscriptImgAtiUrl);
Cypress.Commands.add('testResponseCodeAndType', testResponseCodeAndType);

Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
return originalFn(url, options).then(() => {
// Handle Continue Reading button if it appears when cy.visit() is called
handleContinueReadingButton();
});
});
Cypress.Commands.overwrite(
'visit',
(originalFn, urlOrOptions, ...rest): Cypress.Chainable => {
// Keeps the original signature of cy.visit() intact while allowing for flexible options handling https://docs.cypress.io/api/commands/visit#Usage
const [options] = rest as [Partial<Cypress.VisitOptions>?];

const normalizedVisitOptions =
typeof urlOrOptions === 'string'
? ({
...(options ?? {}),
url: urlOrOptions,
} as Partial<Cypress.VisitOptions> & { url: string })
: (urlOrOptions as Partial<Cypress.VisitOptions> & { url: string });

const { failOnStatusCode = true, headers, url } = normalizedVisitOptions;

const runVisit = (): Cypress.Chainable => {
const visit = originalFn as Cypress.CommandOriginalFn<'visit'> as (
urlOrOptionsParam:
| string
| (Partial<Cypress.VisitOptions> & { url: string }),
optionsParam?: Partial<Cypress.VisitOptions>,
) => Cypress.Chainable;

Copy link
Copy Markdown
Contributor

@emilysaffron emilysaffron Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe nitpicky, but do we really need to be explicitily typing everything like this ( here and in this file in general) ? Does it throw errors without them? If it doesn't my preference would be to not have them in as i think it hurts readability - I know AI really likes to type things explicitly once you tell it it's a typescript file so just thought i'd double check we really need them lol. All good if it actually does throw errors!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I don't like this. I think it will throw and error due to this being a TS file. We could ignore the error. Although I've asked copilot again, and it produced this, which makes it more readable:

    const [options] = rest as [Partial<Cypress.VisitOptions>?];

    const visitUrl =
      typeof urlOrOptions === 'string' ? urlOrOptions : urlOrOptions.url;
    const visitOptionsObj =
      typeof urlOrOptions === 'string' ? (options ?? {}) : urlOrOptions;
    const { failOnStatusCode = true, headers } = visitOptionsObj;

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const originalFnAsAny = originalFn as (...args: any[]) => Cypress.Chainable;
    const runVisit = () => {
      if (typeof urlOrOptions === 'string') {
        return originalFnAsAny(urlOrOptions, options);
      }

      return originalFnAsAny(urlOrOptions);
    };

if (typeof urlOrOptions === 'string') {
return visit(urlOrOptions, options);
}

return visit(normalizedVisitOptions);
};

const checkStatus = (retriesLeft = 2): Cypress.Chainable => {
return cy
.request({
url,
failOnStatusCode: false,
...(headers && { headers }),
})
.then(({ status }) => {
if (status === 200) {
const noOp = () => undefined;
return cy.then(noOp);
}

if (retriesLeft > 0) {
// eslint-disable-next-line cypress/no-unnecessary-waiting
return cy.wait(5000).then(() => checkStatus(retriesLeft - 1));
}

throw new Error(
`Expected status 200 but got ${status} for ${url} after all retries`,
);
});
};

if (!failOnStatusCode) {
return runVisit().then(() => {
// Handle Continue Reading button if it appears when cy.visit() is called
handleContinueReadingButton();
});
}

// Pre-check: Verify the page returns a 200 response before visiting.
// This mitigates Lambda cold-start failures where the first request returns a 500,
// retrying here means before() hooks never surface these transient failures directly.
return checkStatus().then(() => {
return runVisit().then(() => {
// Handle Continue Reading button if it appears when cy.visit() is called
handleContinueReadingButton();
});
});
},
);
8 changes: 0 additions & 8 deletions ws-nextjs-app/cypress/support/helpers/runTestsForPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,6 @@ export default ({
before(() => {
beforeAll.forEach(runBeforeAll => runBeforeAll());

// Ensure that the page is returning a 200 response code
if (failOnStatusCode) {
cy.testResponseCodeAndRetry({
url: path,
headers,
});
}

// Potential fix for a11y tests causing a 'Failed to register a ServiceWorker: The document is in an invalid state.' error.
const removeServiceWorker = (win: Window) => {
if (win.navigator.serviceWorker) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ type RequestPathParts = {
optionalParams?: string[];
};

const oneTimeFailureTarget = {
service: 'pidgin' as Services,
pageType: 'live' as PageTypes,
id: 'c7p765ynk9qt',
};

const simulated500ResponseCount = 2;
let simulated500ResponsesServed = 0;

const constructDataFilePath = ({
service,
pageType,
Expand Down Expand Up @@ -42,7 +51,23 @@ export default async function handler(
res: NextApiResponse,
) {
try {
const dataFilePath = constructDataFilePath(req.query as RequestPathParts);
const requestPathParts = req.query as RequestPathParts;

const isConfiguredFailureRequest =
requestPathParts.service === oneTimeFailureTarget.service &&
requestPathParts.pageType === oneTimeFailureTarget.pageType &&
requestPathParts.id === oneTimeFailureTarget.id;

if (
isConfiguredFailureRequest &&
simulated500ResponsesServed < simulated500ResponseCount
) {
simulated500ResponsesServed += 1;

return res.status(500).send({ error: 'Simulated error for this asset' });
}

const dataFilePath = constructDataFilePath(requestPathParts);
const pageData = await fs.readFile(dataFilePath, {
encoding: 'utf8',
});
Expand Down
Loading