@manufac/general-tests 0.5.1 → 0.9.0

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.
Files changed (62) hide show
  1. package/README.md +88 -0
  2. package/dist/config/projects.d.ts +3 -1
  3. package/dist/config/projects.js +4 -2
  4. package/dist/fixtures/index.d.ts +2 -1
  5. package/dist/fixtures/index.js +1 -0
  6. package/dist/schema/index.d.ts +13 -0
  7. package/dist/schema/index.js +7 -0
  8. package/dist/specs/cta-destinations.spec.js +65 -0
  9. package/dist/specs/page-visual-regression.spec.js +68 -0
  10. package/dist/specs/{visual-regression.spec.js → storybook-visual-regression.spec.js} +7 -0
  11. package/dist/specs/utils.d.ts +4 -158
  12. package/dist/specs/utils.js +27 -856
  13. package/package.json +1 -1
  14. package/dist/specs/desktop-only/browser-zoom.spec.js +0 -26
  15. package/dist/specs/mobile-only/ios-safari-toolbar.spec.js +0 -62
  16. package/dist/specs/mobile-only/ios-safe-areas.spec.js +0 -36
  17. package/dist/specs/mobile-only/navigation.spec.d.ts +0 -1
  18. package/dist/specs/mobile-only/navigation.spec.js +0 -40
  19. package/dist/specs/mobile-only/orientation.spec.d.ts +0 -1
  20. package/dist/specs/mobile-only/orientation.spec.js +0 -46
  21. package/dist/specs/mobile-only/readability.spec.d.ts +0 -1
  22. package/dist/specs/mobile-only/readability.spec.js +0 -14
  23. package/dist/specs/mobile-only/soft-keyboard.spec.d.ts +0 -1
  24. package/dist/specs/mobile-only/soft-keyboard.spec.js +0 -43
  25. package/dist/specs/mobile-only/touch-targets.spec.d.ts +0 -1
  26. package/dist/specs/mobile-only/touch-targets.spec.js +0 -12
  27. package/dist/specs/shared/cta.spec.d.ts +0 -1
  28. package/dist/specs/shared/cta.spec.js +0 -199
  29. package/dist/specs/shared/forms/autofill.spec.d.ts +0 -1
  30. package/dist/specs/shared/forms/autofill.spec.js +0 -28
  31. package/dist/specs/shared/forms/double-submit.spec.d.ts +0 -1
  32. package/dist/specs/shared/forms/double-submit.spec.js +0 -25
  33. package/dist/specs/shared/forms/input-edge.spec.d.ts +0 -1
  34. package/dist/specs/shared/forms/input-edge.spec.js +0 -60
  35. package/dist/specs/shared/forms/invalid-input.spec.d.ts +0 -1
  36. package/dist/specs/shared/forms/invalid-input.spec.js +0 -41
  37. package/dist/specs/shared/forms/native-inputs.spec.d.ts +0 -1
  38. package/dist/specs/shared/forms/native-inputs.spec.js +0 -37
  39. package/dist/specs/shared/forms/required-field.spec.d.ts +0 -1
  40. package/dist/specs/shared/forms/required-field.spec.js +0 -39
  41. package/dist/specs/shared/forms/thankyou-confirm.spec.d.ts +0 -1
  42. package/dist/specs/shared/forms/thankyou-confirm.spec.js +0 -34
  43. package/dist/specs/shared/hero-fold.spec.d.ts +0 -1
  44. package/dist/specs/shared/hero-fold.spec.js +0 -40
  45. package/dist/specs/shared/image-quality.spec.d.ts +0 -1
  46. package/dist/specs/shared/image-quality.spec.js +0 -13
  47. package/dist/specs/shared/media-embeds/carousel-slider.spec.d.ts +0 -1
  48. package/dist/specs/shared/media-embeds/carousel-slider.spec.js +0 -31
  49. package/dist/specs/shared/media-embeds/cookie-consent.spec.d.ts +0 -1
  50. package/dist/specs/shared/media-embeds/cookie-consent.spec.js +0 -27
  51. package/dist/specs/shared/media-embeds/third-party-embeds.spec.d.ts +0 -1
  52. package/dist/specs/shared/media-embeds/third-party-embeds.spec.js +0 -27
  53. package/dist/specs/shared/media-embeds/video-playback.spec.d.ts +0 -1
  54. package/dist/specs/shared/media-embeds/video-playback.spec.js +0 -54
  55. package/dist/specs/shared/sticky-elements.spec.d.ts +0 -1
  56. package/dist/specs/shared/sticky-elements.spec.js +0 -13
  57. package/dist/specs/sticky-utils.d.ts +0 -8
  58. package/dist/specs/sticky-utils.js +0 -175
  59. package/dist/specs/visual-regression.spec.d.ts +0 -1
  60. /package/dist/specs/{desktop-only/browser-zoom.spec.d.ts → cta-destinations.spec.d.ts} +0 -0
  61. /package/dist/specs/{mobile-only/ios-safari-toolbar.spec.d.ts → page-visual-regression.spec.d.ts} +0 -0
  62. /package/dist/specs/{mobile-only/ios-safe-areas.spec.d.ts → storybook-visual-regression.spec.d.ts} +0 -0
@@ -1,60 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../../fixtures");
4
- const utils_1 = require("../../utils");
5
- const test_1 = require("@playwright/test");
6
- fixtures_1.test.describe("Forms - Input Edge Cases", () => {
7
- (0, fixtures_1.test)("FM-08 Name field accepts a 200+ character string without breaking layout", async ({ page, targetPage }) => {
8
- await page.goto(targetPage);
9
- const nameField = page.getByLabel(/name/i);
10
- const nameFieldCount = await nameField.count();
11
- if (nameFieldCount === 0) {
12
- fixtures_1.test.skip(true, "No Name field present on this page");
13
- }
14
- await (0, utils_1.fillFormFields)(page);
15
- const longName = "A".repeat(utils_1.ValidUserInfo.LongNameCharacterCount);
16
- await nameField.first().fill(longName);
17
- const isOverflowingBeforeSubmit = await (0, utils_1.hasHorizontalScrollbar)(page);
18
- (0, test_1.expect)(isOverflowingBeforeSubmit, "Page should not have overflowing/broken layout after a very long name is entered").toBe(false);
19
- const submitButton = page.locator('button[type="submit"], input[type="submit"]').first();
20
- await (0, test_1.expect)(submitButton, "Submit button should remain visible with a long name entered").toBeVisible();
21
- await submitButton.click();
22
- const successState = page.getByText(/thank you|success|confirmation/i).first();
23
- await (0, test_1.expect)(successState, "Form should submit successfully with a very long name entered").toBeVisible();
24
- const isOverflowingAfterSubmit = await (0, utils_1.hasHorizontalScrollbar)(page);
25
- (0, test_1.expect)(isOverflowingAfterSubmit, "Confirmation should not have overflowing/broken layout after a very long name was submitted").toBe(false);
26
- });
27
- (0, fixtures_1.test)("FM-08 Name field with emoji is accepted or cleanly rejected", async ({ page, targetPage, consoleErrors }) => {
28
- await page.goto(targetPage);
29
- const nameField = page.getByLabel(/name/i);
30
- const nameFieldCount = await nameField.count();
31
- if (nameFieldCount === 0) {
32
- fixtures_1.test.skip(true, "No Name field present on this page");
33
- }
34
- await nameField.first().fill(utils_1.ValidUserInfo.EmojiTestName);
35
- const nameValue = await nameField.first().inputValue();
36
- const isEmojiAccepted = nameValue === utils_1.ValidUserInfo.EmojiTestName;
37
- // Indicates the emoji character was not preserved in the field value (stripped or cleared by the input).
38
- const isEmojiRejected = nameValue === "" || !nameValue.includes("😀");
39
- (0, test_1.expect)(isEmojiAccepted || isEmojiRejected, "Emoji in the name field should be accepted as typed or cleanly rejected (no garbled/crashed display)").toBe(true);
40
- (0, test_1.expect)(consoleErrors, `Entering emoji in the name field should not cause a console error:\n${consoleErrors.join("\n")}`).toHaveLength(0);
41
- });
42
- (0, fixtures_1.test)("FM-08 Email with leading/trailing spaces is trimmed and submits successfully", async ({ page, targetPage }) => {
43
- await page.goto(targetPage);
44
- const form = page.locator("form");
45
- if ((await form.count()) === 0) {
46
- fixtures_1.test.skip(true, "No form present on this page");
47
- }
48
- await (0, utils_1.fillFormFields)(page);
49
- const emailField = page.getByLabel(/email/i);
50
- await emailField.fill(utils_1.ValidUserInfo.PaddedTestEmail);
51
- await emailField.blur();
52
- // Checked on blur since that's when most forms trim client-side; a form that only trims
53
- // server-side at submission time (also valid per the plan's wording) wouldn't show it here.
54
- const trimmedEmail = await emailField.inputValue();
55
- (0, test_1.expect)(trimmedEmail, `Leading/trailing spaces around the email should be trimmed; field still shows "${trimmedEmail}"`).toBe(utils_1.ValidUserInfo.PaddedTestEmail.trim());
56
- await page.locator('button[type="submit"], input[type="submit"]').first().click();
57
- const successState = page.getByText(/thank you|success|confirmation/i).first();
58
- await (0, test_1.expect)(successState, "Form should submit successfully after trimming leading/trailing spaces around the email").toBeVisible();
59
- });
60
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,41 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../../fixtures");
4
- const utils_1 = require("../../utils");
5
- const test_1 = require("@playwright/test");
6
- fixtures_1.test.describe("Forms - Invalid Input", () => {
7
- utils_1.InvalidInputCases.forEach((invalidCase) => {
8
- (0, fixtures_1.test)(`FM-03 Invalid input "${invalidCase.invalidValue}" in ${invalidCase.fieldLabel.toString()} field is rejected (if present)`, async ({ page, targetPage, }) => {
9
- await page.goto(targetPage);
10
- const form = page.locator("form");
11
- if ((await form.count()) === 0) {
12
- fixtures_1.test.skip(true, "No form present on this page");
13
- }
14
- await form.first().scrollIntoViewIfNeeded();
15
- const fieldLocator = form.getByLabel(invalidCase.fieldLabel);
16
- if ((await fieldLocator.count()) === 0 || !(await fieldLocator.isVisible())) {
17
- fixtures_1.test.skip(true, `${invalidCase.fieldLabel.toString()} field is not present on this form`);
18
- }
19
- // Fill valid data in all other required fields
20
- await (0, utils_1.fillOtherRequiredFieldsValidly)(page, invalidCase.fieldLabel);
21
- // Fill invalid value in the field under test
22
- await fieldLocator.first().fill(invalidCase.invalidValue);
23
- // Attempt submit
24
- const submitButton = form.locator('button[type="submit"], input[type="submit"]');
25
- await submitButton.click();
26
- // Assert rejection - confirmation should NOT appear
27
- const confirmation = page.getByText(/thank you|success|confirmation/i);
28
- await (0, test_1.expect)(confirmation).not.toBeVisible({ timeout: 5000 });
29
- // Assert validation message or invalid state
30
- await (0, test_1.expect)(async () => {
31
- const validationError = page.getByText(invalidCase.expectedErrorText);
32
- const isErrorVisible = await validationError.isVisible();
33
- const isAriaValid = (await fieldLocator.first().getAttribute("aria-invalid")) !== "true";
34
- const isNativelyValid = await fieldLocator.first().evaluate((node) => {
35
- return node.checkValidity();
36
- });
37
- (0, test_1.expect)(isErrorVisible || !isAriaValid || !isNativelyValid).toBeTruthy();
38
- }).toPass({ timeout: 5000 });
39
- });
40
- });
41
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,37 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../../fixtures");
4
- const test_1 = require("@playwright/test");
5
- fixtures_1.test.describe("Forms - Native Inputs", () => {
6
- (0, fixtures_1.test)("XB-04 Date and number inputs reject invalid values", async ({ page, targetPage }) => {
7
- await page.goto(targetPage);
8
- // Locate all date and number inputs
9
- const nativeInputs = page.locator('input[type="date"], input[type="number"]');
10
- const inputCount = await nativeInputs.count();
11
- if (inputCount === 0) {
12
- fixtures_1.test.skip(true, "No date or number inputs found on the page");
13
- }
14
- const allInputs = await nativeInputs.all();
15
- const invalidValues = {
16
- number: "notanumber",
17
- date: "02302026",
18
- };
19
- for (const inputLocator of allInputs) {
20
- const inputType = await inputLocator.getAttribute("type");
21
- const invalidValue = inputType !== null ? (invalidValues[inputType] ?? "") : "";
22
- // We use click and keyboard.type to bypass Playwright's strict fill checks for invalid types
23
- // (Playwright's fill() throws if you try to put letters in a number field).
24
- // This simulates a user typing invalid data to see if the browser's native UI rejects it.
25
- await inputLocator.click();
26
- await page.keyboard.type(invalidValue);
27
- const isRejectedByBrowser = await inputLocator.evaluate((element) => {
28
- // If the browser natively rejects the input because of its type constraint,
29
- // the value property will often be an empty string, or validity.badInput will be true.
30
- const isBadInput = element.validity.badInput;
31
- const isValueRejected = element.value === "";
32
- return isBadInput || isValueRejected;
33
- });
34
- (0, test_1.expect)(isRejectedByBrowser, `Input of type "${inputType ?? "unknown"}" should reject invalid value "${invalidValue}"`).toBe(true);
35
- }
36
- });
37
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,39 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../../fixtures");
4
- const utils_1 = require("../../utils");
5
- const test_1 = require("@playwright/test");
6
- // Ref: https://playwright.dev/docs/test-parameterize#parameterized-tests
7
- fixtures_1.test.describe("Forms", () => {
8
- utils_1.FieldValueMappings.forEach((targetField) => {
9
- (0, fixtures_1.test)(`FM-02 Required field validation blocks empty ${targetField.label.source}`, async ({ page, targetPage, }) => {
10
- await page.goto(targetPage);
11
- const fieldLocator = page.getByLabel(targetField.label);
12
- const fieldCount = await fieldLocator.count();
13
- if (fieldCount === 0) {
14
- fixtures_1.test.skip(true, `No ${targetField.label.source} field present on this page`);
15
- }
16
- const isRequired = await (0, utils_1.isFieldRequired)(fieldLocator.first());
17
- const isEmailField = targetField.label.source === "email";
18
- if (!isRequired && !isEmailField) {
19
- fixtures_1.test.skip(true, `${targetField.label.source} field is not marked required on this page`);
20
- }
21
- // Fill every OTHER required field validly and deliberately leave the target field
22
- // untouched, rather than filling it and clearing it afterward — some picker widgets
23
- // (e.g. a date/time field backed by a <button>, not a native input) can't be cleared
24
- // with .clear() at all, so never filling them in the first place avoids that entirely.
25
- await (0, utils_1.fillOtherRequiredFieldsValidly)(page, targetField.label);
26
- const submitButtons = page.locator('button[type="submit"], input[type="submit"]');
27
- await submitButtons.first().click();
28
- // The error may render a moment after the click resolves (e.g. a brief re-render before
29
- // the field's aria-describedby/error text attaches), so poll rather than check once.
30
- await (0, test_1.expect)(async () => {
31
- const isErrorPresent = await (0, utils_1.hasVisibleErrorNearField)(fieldLocator.first());
32
- (0, test_1.expect)(isErrorPresent, `No visible inline error next to the empty ${targetField.label.source} field after submit`).toBe(true);
33
- }).toPass({ timeout: 5000 });
34
- const genericError = page.getByText(/form error/i);
35
- const genericErrorCount = await genericError.count();
36
- (0, test_1.expect)(genericErrorCount, `Error message for ${targetField.label.source} should be specific (e.g. "Email is required"), not a generic "form error"`).toBe(0);
37
- });
38
- });
39
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,34 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../../fixtures");
4
- const utils_1 = require("../../utils");
5
- const test_1 = require("@playwright/test");
6
- fixtures_1.test.describe("Forms - Thank You Confirm", () => {
7
- (0, fixtures_1.test)("FM-07 Confirmation state matches spec and does not resubmit on Back/Forward", async ({ page, targetPage }) => {
8
- await page.goto(targetPage);
9
- const form = page.locator("form");
10
- if ((await form.count()) === 0) {
11
- fixtures_1.test.skip(true, "No form present on this page");
12
- }
13
- await (0, utils_1.fillFormFields)(page);
14
- await page.locator('button[type="submit"], input[type="submit"]').first().click();
15
- // A toast/notification commonly has both a title and a description that separately match
16
- // this pattern (e.g. "Success" + "...submitted successfully") — .first() avoids a strict-mode
17
- // violation when that happens, rather than assuming there's ever only one matching element.
18
- const confirmation = page.getByText(/thank you|success|confirmation/i).first();
19
- await (0, test_1.expect)(confirmation, "Confirmation message/page did not appear or did not match the design spec").toBeVisible({ timeout: 10_000 });
20
- let hasResubmitDialog = false;
21
- page.on("dialog", (dialog) => {
22
- hasResubmitDialog = true;
23
- void dialog.dismiss();
24
- });
25
- await page.goBack();
26
- (0, test_1.expect)(hasResubmitDialog, 'Browser Back should NOT show a "resubmit form?" dialog').toBe(false);
27
- // Not re-checking that the confirmation is still visible here: a toast-style confirmation
28
- // (as opposed to a persistent thank-you page) is expected to auto-dismiss after a few
29
- // seconds, and the Back/Forward round trip can easily take longer than that. The actual
30
- // FM-07 concern — no resubmission — is fully covered by the dialog check below.
31
- await page.goForward();
32
- (0, test_1.expect)(hasResubmitDialog, 'Browser Forward should NOT show a "resubmit form?" dialog').toBe(false);
33
- });
34
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,40 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../fixtures");
4
- const test_1 = require("@playwright/test");
5
- (0, fixtures_1.test)("RS-03 Hero section above the fold", async ({ page, targetPage }) => {
6
- await page.goto(targetPage);
7
- const headline = page.getByRole("heading", { level: 1 }).first();
8
- await (0, test_1.expect)(headline, "Main headline not visible without scrolling").toBeVisible();
9
- await (0, test_1.expect)(headline, "Main headline not fully within the fold").toBeInViewport({
10
- ratio: 1,
11
- });
12
- const ctaRegex = /get started|book a demo|contact us|try|sign up|request|start|buy|explore|join|register|learn|get|access|download|schedule|book|discuss/i;
13
- // Restrict to elements that appear before the first <form> on the page, so the hero's
14
- // CTA can never accidentally resolve to a submit button belonging to the contact form.
15
- // boundingBox() auto-waits for a matching element, so it must only be called once a form
16
- // is confirmed present — otherwise a page with no form at all would hang until timeout.
17
- const contactForm = page.locator("form").first();
18
- const hasContactForm = (await contactForm.count()) > 0;
19
- const formBoundingBox = hasContactForm ? await contactForm.boundingBox() : null;
20
- const allMatches = page.getByRole("link", { name: ctaRegex }).or(page.getByRole("button", { name: ctaRegex }));
21
- const matchCount = await allMatches.count();
22
- let primaryCTA = allMatches.first();
23
- if (formBoundingBox !== null) {
24
- let isFound = false;
25
- for (let index = 0; index < matchCount; index++) {
26
- if (!isFound) {
27
- const candidate = allMatches.nth(index);
28
- const candidateBox = await candidate.boundingBox();
29
- if (candidateBox !== null && candidateBox.y < formBoundingBox.y) {
30
- primaryCTA = candidate;
31
- isFound = true;
32
- }
33
- }
34
- }
35
- }
36
- await (0, test_1.expect)(primaryCTA, "Primary CTA not visible without scrolling").toBeVisible();
37
- await (0, test_1.expect)(primaryCTA, "Primary CTA not fully within the fold").toBeInViewport({
38
- ratio: 1,
39
- });
40
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../fixtures");
4
- const utils_1 = require("../utils");
5
- const test_1 = require("@playwright/test");
6
- (0, fixtures_1.test)("RS-08 Images are not blurry/pixelated/stretched, and load an appropriately sized file for the viewport", async ({ page, targetPage, }) => {
7
- await page.goto(targetPage);
8
- const imageSizeIssues = await (0, utils_1.findImageSizeIssues)(page);
9
- const upscaledImages = imageSizeIssues.filter(utils_1.isImageUpscaled);
10
- (0, test_1.expect)(upscaledImages, "Image(s) appear stretched/upscaled at this viewport").toEqual([]);
11
- const oversizedImages = imageSizeIssues.filter(utils_1.isImageOversized);
12
- (0, test_1.expect)(oversizedImages, "Image(s) loaded a file much larger than needed for this viewport — responsive srcset may not be working").toEqual([]);
13
- });
@@ -1,31 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../../fixtures");
4
- const utils_1 = require("../../utils");
5
- const test_1 = require("@playwright/test");
6
- fixtures_1.test.describe("Media Embeds - Carousel Slider", () => {
7
- (0, fixtures_1.test)("MD-02 Carousel is reachable through all slides without blank slides", async ({ page, isMobile, targetPage, }) => {
8
- fixtures_1.test.slow();
9
- await page.goto(targetPage);
10
- await page.waitForLoadState("load");
11
- const carousels = page.locator('[data-carousel], [role="region"][aria-roledescription="carousel"]');
12
- const carouselCount = await carousels.count();
13
- fixtures_1.test.skip(carouselCount === 0, "No carousel/slider present on this page");
14
- const carouselsAll = await carousels.all();
15
- await Promise.all(carouselsAll.map(async (carousel, carouselIndex) => {
16
- await (0, utils_1.traverseSlidesForward)(carousel, carouselIndex, isMobile);
17
- if (isMobile) {
18
- await (0, utils_1.checkSwipeBackward)(carousel, carouselIndex);
19
- }
20
- else {
21
- await (0, utils_1.checkAllDots)(carousel, carouselIndex);
22
- }
23
- }));
24
- const autoAdvanceWaitMS = 30_000;
25
- await page.waitForTimeout(autoAdvanceWaitMS);
26
- await Promise.all(carouselsAll.map(async (carousel, carouselIndex) => {
27
- const isEmptyFrameAfterWait = await (0, utils_1.hasEmptySlideFrame)(carousel);
28
- (0, test_1.expect)(isEmptyFrameAfterWait, `Carousel at index ${carouselIndex.toString()}: Slide frame was blank after 30s untouched`).toBe(false);
29
- }));
30
- });
31
- });
@@ -1,27 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../../fixtures");
4
- const test_1 = require("@playwright/test");
5
- fixtures_1.test.describe("Media & embeds", () => {
6
- (0, fixtures_1.test)("MD-04 Cookie consent banner appears on first visit and can be dismissed", async ({ page, targetPage, }) => {
7
- await page.goto(targetPage);
8
- // Look for common cookie banner text and an accept button
9
- const acceptButton = page
10
- .getByRole("button", { name: /accept|got it|agree/i })
11
- .filter({ hasText: /cookie|accept/i })
12
- .first();
13
- const isBannerVisible = await acceptButton.isVisible();
14
- // isBannerVisible is true when the cookie consent banner is present and visible on the page
15
- if (!isBannerVisible) {
16
- fixtures_1.test.skip(true, "No cookie consent banner found on this page");
17
- }
18
- // Dismiss it
19
- await acceptButton.click();
20
- // Button/banner should disappear
21
- await (0, test_1.expect)(acceptButton).toBeHidden();
22
- // Reload page
23
- await page.reload();
24
- // Banner should not reappear (Playwright context persists cookies within a single test by default)
25
- await (0, test_1.expect)(acceptButton).toBeHidden();
26
- });
27
- });
@@ -1,27 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../../fixtures");
4
- const utils_1 = require("../../utils");
5
- const test_1 = require("@playwright/test");
6
- fixtures_1.test.describe("Media Embeds - Third Party", () => {
7
- (0, fixtures_1.test)("MD-03 Third-party embeds render without overflow and remain interactive", async ({ page, targetPage, consoleErrors, }) => {
8
- await page.goto(targetPage);
9
- const embeds = page.locator("iframe, embed, object");
10
- const embedCount = await embeds.count();
11
- fixtures_1.test.skip(embedCount === 0, "No third-party embeds present on this page");
12
- const isOverflowing = await (0, utils_1.hasHorizontalScrollbar)(page);
13
- (0, test_1.expect)(isOverflowing, "Page should not have horizontal overflow with embeds present").toBe(false);
14
- // Note: MD-03 specifies that broken embeds shouldn't leave a "giant empty gap [that] breaks the layout".
15
- // Detecting "blank because the embed failed to load" vs "legitimately blank/white content" purely
16
- // from the outer element's geometry isn't reliably possible without provider-specific knowledge.
17
- // If an embed shrinks to 0x0, that is considered a graceful failure, not a bug.
18
- // This is a known automation limitation (P2). We rely on the layout/overflow checks above and below.
19
- const heading = page.getByRole("heading", { level: 1 }).first();
20
- await (0, test_1.expect)(heading, "Main heading should remain visible with embeds present").toBeVisible();
21
- for (let embedIndex = 0; embedIndex < embedCount; embedIndex++) {
22
- await embeds.nth(embedIndex).click({ timeout: 5_000 });
23
- }
24
- (0, test_1.expect)(consoleErrors, `Interacting with an embed should not cause a console error:\n${consoleErrors.join("\n")}`).toHaveLength(0);
25
- await (0, test_1.expect)(heading, "Main heading should still be visible after interacting with each embed").toBeVisible();
26
- });
27
- });
@@ -1,54 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../../fixtures");
4
- const utils_1 = require("../../utils");
5
- const test_1 = require("@playwright/test");
6
- fixtures_1.test.describe("Media Embeds - Video Playback", () => {
7
- (0, fixtures_1.test)("MD-01 Video playback controls work and autoplay (if present) is muted", async ({ page, targetPage, }) => {
8
- await page.goto(targetPage);
9
- const videos = page.locator("video");
10
- const videoCount = await videos.count();
11
- fixtures_1.test.skip(videoCount === 0, "No video element present on this page");
12
- const allVideos = await videos.all();
13
- await Promise.all(allVideos.map(async (video, index) => {
14
- const isAutoplaying = await (0, utils_1.isVideoAutoplaying)(video);
15
- if (isAutoplaying) {
16
- const isMuted = await (0, utils_1.isVideoMuted)(video);
17
- (0, test_1.expect)(isMuted, `Autoplaying video at index ${index.toString()} must be muted (autoplay with sound is a failure, especially on mobile)`).toBe(true);
18
- }
19
- await video.evaluate((element) => {
20
- element.pause();
21
- });
22
- const isPausedAfterPause = await video.evaluate((element) => {
23
- return element.paused;
24
- });
25
- (0, test_1.expect)(isPausedAfterPause, `Video at index ${index.toString()} should pause when pause is triggered`).toBe(true);
26
- await video.evaluate((element) => {
27
- void element.play();
28
- });
29
- const isPlayingAfterPlay = await video.evaluate((element) => {
30
- return !element.paused;
31
- });
32
- (0, test_1.expect)(isPlayingAfterPlay, `Video at index ${index.toString()} should play when play is triggered`).toBe(true);
33
- await video.evaluate((element) => {
34
- element.muted = false;
35
- });
36
- const isUnmuted = await video.evaluate((element) => {
37
- return !element.muted;
38
- });
39
- (0, test_1.expect)(isUnmuted, `Video at index ${index.toString()} should be unmutable via its controls`).toBe(true);
40
- const initialTime = await video.evaluate((element) => {
41
- return element.currentTime;
42
- });
43
- const seek = initialTime + 1;
44
- await video.evaluate((element, seekTo) => {
45
- element.currentTime = seekTo;
46
- }, seek);
47
- const currentTimeAfterSeek = await video.evaluate((element) => {
48
- return element.currentTime;
49
- });
50
- const timeDifference = Math.abs(currentTimeAfterSeek - initialTime);
51
- (0, test_1.expect)(timeDifference, `Video at index ${index.toString()} should have a different current time after seeking`).toBeGreaterThan(0);
52
- }));
53
- });
54
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const fixtures_1 = require("../../fixtures");
4
- const sticky_utils_1 = require("../sticky-utils");
5
- const test_1 = require("@playwright/test");
6
- (0, fixtures_1.test)("RS-06 Sticky elements (if present) stay pinned and don't cover content or overlap the footer", async ({ page, targetPage, }) => {
7
- await page.goto(targetPage);
8
- const { stickyElementCount, flicker, coversContent, overlapsFooter } = await (0, sticky_utils_1.checkStickyElements)(page);
9
- fixtures_1.test.skip(stickyElementCount === 0, "No sticky/fixed elements present");
10
- (0, test_1.expect)(flicker, `Sticky element did not stay pinned:\n${flicker.join("\n")}`).toEqual([]);
11
- (0, test_1.expect)(coversContent, `Sticky element covered body text or form fields:\n${coversContent.join("\n")}`).toEqual([]);
12
- (0, test_1.expect)(overlapsFooter, `Sticky element overlapped the footer at the bottom of the page:\n${overlapsFooter.join("\n")}`).toEqual([]);
13
- });
@@ -1,8 +0,0 @@
1
- import type { Page } from "@playwright/test";
2
- export interface StickyIssues {
3
- stickyElementCount: number;
4
- flicker: string[];
5
- coversContent: string[];
6
- overlapsFooter: string[];
7
- }
8
- export declare function checkStickyElements(page: Page): Promise<StickyIssues>;