@manufac/general-tests 0.0.2 → 0.1.1

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.
package/README.md CHANGED
@@ -24,18 +24,38 @@ Then wire it into the app's `playwright.config.ts` using the project-builder hel
24
24
 
25
25
  ```ts
26
26
  import { defineConfig } from "@playwright/test";
27
- import { createDesktopProject, createMobileProject } from "@manufac/general-tests/config/projects.js";
27
+ import { Breakpoints, createProject } from "@manufac/general-tests/config/projects.js";
28
28
 
29
29
  export default defineConfig({
30
30
  use: {
31
31
  baseURL: "http://localhost:8080",
32
32
  },
33
33
  projects: [
34
- createDesktopProject("Desktop Chrome", "Desktop Chrome"),
35
- createDesktopProject("Desktop Safari", "Desktop Safari"),
36
- createDesktopProject("Desktop Firefox", "Desktop Firefox"),
37
- createMobileProject("Mobile Safari", "iPhone 12"),
38
- createMobileProject("Mobile Chrome", "Pixel 5"),
34
+ createProject({
35
+ name: "Desktop Chrome",
36
+ device: "Desktop Chrome",
37
+ configuration: { projectType: "desktop", breakpoint: Breakpoints.Desktop },
38
+ }),
39
+ createProject({
40
+ name: "Desktop Safari",
41
+ device: "Desktop Safari",
42
+ configuration: { projectType: "desktop", breakpoint: Breakpoints.Desktop },
43
+ }),
44
+ createProject({
45
+ name: "Desktop Firefox",
46
+ device: "Desktop Firefox",
47
+ configuration: { projectType: "desktop", breakpoint: Breakpoints.Desktop },
48
+ }),
49
+ createProject({
50
+ name: "Mobile Safari",
51
+ device: "iPhone 12",
52
+ configuration: { projectType: "mobile", breakpoint: Breakpoints.MobileM },
53
+ }),
54
+ createProject({
55
+ name: "Mobile Chrome",
56
+ device: "Pixel 5",
57
+ configuration: { projectType: "mobile", breakpoint: Breakpoints.MobileM },
58
+ }),
39
59
  ],
40
60
  webServer: {
41
61
  command: "npm run start",
@@ -45,14 +65,18 @@ export default defineConfig({
45
65
  });
46
66
  ```
47
67
 
48
- - `createDesktopProject(name, device, breakpoint?)` runs the `shared` + `desktop-only` specs under the given Playwright device, defaulting to the Desktop breakpoint (1440x900).
49
- - `createMobileProject(name, device, breakpoint?)` runs the `shared` + `mobile-only` specs, defaulting to the Mobile M breakpoint (390x844).
50
- - Both accept an optional third `breakpoint` argument — one of `Breakpoints.MobileS/MobileM/Tablet/Laptop/Desktop/Wide` (Section 3 of the test plan) — to test at a different viewport.
68
+ - `createProject({ name, device, configuration })` builds a Playwright project for the given device.
69
+ - `configuration.projectType: "desktop"` runs the `shared` + `desktop-only` specs, and requires `configuration.breakpoint: Breakpoints.Desktop` (1440x900).
70
+ - `configuration.projectType: "mobile"` runs the `shared` + `mobile-only` specs, and requires `configuration.breakpoint: Breakpoints.MobileM` (390x844).
51
71
 
52
72
  By default every spec runs against `/`. To point a project at a different page, override the `targetPage` fixture option:
53
73
 
54
74
  ```ts
55
- createDesktopProject("Desktop Chrome — pricing", "Desktop Chrome"),
75
+ createProject({
76
+ name: "Desktop Chrome — pricing",
77
+ device: "Desktop Chrome",
78
+ configuration: { projectType: "desktop", breakpoint: Breakpoints.Desktop },
79
+ }),
56
80
  // then, in that project's `use`:
57
81
  use: { targetPage: "/pricing" }
58
82
  ```
@@ -37,5 +37,23 @@ export declare const Breakpoints: {
37
37
  readonly height: 1080;
38
38
  };
39
39
  };
40
- export declare function createDesktopProject(name: string, device: keyof typeof devices, breakpoint?: Breakpoint): Project;
41
- export declare function createMobileProject(name: string, device: keyof typeof devices, breakpoint?: Breakpoint): Project;
40
+ declare const ProjectTypes: {
41
+ readonly Mobile: "mobile";
42
+ readonly Desktop: "desktop";
43
+ };
44
+ export type ProjectType = (typeof ProjectTypes)[keyof typeof ProjectTypes];
45
+ export interface MobileConfig {
46
+ projectType: typeof ProjectTypes.Mobile;
47
+ breakpoint: typeof Breakpoints.MobileM;
48
+ }
49
+ export interface DesktopConfig {
50
+ projectType: typeof ProjectTypes.Desktop;
51
+ breakpoint: typeof Breakpoints.Desktop;
52
+ }
53
+ export interface CreateProjectParams {
54
+ name: string;
55
+ device: keyof typeof devices;
56
+ configuration: MobileConfig | DesktopConfig;
57
+ }
58
+ export declare function createProject({ configuration, device, name }: CreateProjectParams): Project;
59
+ export {};
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Breakpoints = void 0;
4
- exports.createDesktopProject = createDesktopProject;
5
- exports.createMobileProject = createMobileProject;
4
+ exports.createProject = createProject;
6
5
  const test_1 = require("@playwright/test");
7
6
  const node_path_1 = require("node:path");
8
7
  // Section 3 of the Landing Page Test Plan — breakpoints / viewports.
@@ -19,19 +18,21 @@ const SpecsDirectory = (0, node_path_1.join)(__dirname, "..", "specs");
19
18
  const SharedTestMatch = "shared/**/*.spec.js";
20
19
  const DesktopOnlyTestMatch = "desktop-only/**/*.spec.js";
21
20
  const MobileOnlyTestMatch = "mobile-only/**/*.spec.js";
22
- function createDesktopProject(name, device, breakpoint = exports.Breakpoints.Desktop) {
23
- return {
24
- name,
25
- testDir: SpecsDirectory,
26
- testMatch: [SharedTestMatch, DesktopOnlyTestMatch],
27
- use: { ...test_1.devices[device], viewport: { width: breakpoint.width, height: breakpoint.height } },
28
- };
29
- }
30
- function createMobileProject(name, device, breakpoint = exports.Breakpoints.MobileM) {
21
+ const ProjectTypes = {
22
+ Mobile: "mobile",
23
+ Desktop: "desktop",
24
+ };
25
+ function createProject({ configuration, device, name }) {
31
26
  return {
32
27
  name,
33
28
  testDir: SpecsDirectory,
34
- testMatch: [SharedTestMatch, MobileOnlyTestMatch],
35
- use: { ...test_1.devices[device], viewport: { width: breakpoint.width, height: breakpoint.height } },
29
+ testMatch: [
30
+ SharedTestMatch,
31
+ configuration.projectType === ProjectTypes.Desktop ? DesktopOnlyTestMatch : MobileOnlyTestMatch,
32
+ ],
33
+ use: {
34
+ ...test_1.devices[device],
35
+ viewport: { width: configuration.breakpoint.width, height: configuration.breakpoint.height },
36
+ },
36
37
  };
37
38
  }
@@ -16,10 +16,10 @@ fixtures_1.test.describe("Forms - Input Edge Cases", () => {
16
16
  await nameField.first().fill(longName);
17
17
  const isOverflowingBeforeSubmit = await (0, utils_1.hasHorizontalScrollbar)(page);
18
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.getByRole("button", { name: /submit/i });
19
+ const submitButton = page.locator('button[type="submit"], input[type="submit"]').first();
20
20
  await (0, test_1.expect)(submitButton, "Submit button should remain visible with a long name entered").toBeVisible();
21
21
  await submitButton.click();
22
- const successState = page.getByText(/thank you|success|confirmation/i);
22
+ const successState = page.getByText(/thank you|success|confirmation/i).first();
23
23
  await (0, test_1.expect)(successState, "Form should submit successfully with a very long name entered").toBeVisible();
24
24
  const isOverflowingAfterSubmit = await (0, utils_1.hasHorizontalScrollbar)(page);
25
25
  (0, test_1.expect)(isOverflowingAfterSubmit, "Confirmation should not have overflowing/broken layout after a very long name was submitted").toBe(false);
@@ -41,6 +41,10 @@ fixtures_1.test.describe("Forms - Input Edge Cases", () => {
41
41
  });
42
42
  (0, fixtures_1.test)("FM-08 Email with leading/trailing spaces is trimmed and submits successfully", async ({ page, targetPage }) => {
43
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
+ }
44
48
  await (0, utils_1.fillFormFields)(page);
45
49
  const emailField = page.getByLabel(/email/i);
46
50
  await emailField.fill(utils_1.ValidUserInfo.PaddedTestEmail);
@@ -49,8 +53,8 @@ fixtures_1.test.describe("Forms - Input Edge Cases", () => {
49
53
  // server-side at submission time (also valid per the plan's wording) wouldn't show it here.
50
54
  const trimmedEmail = await emailField.inputValue();
51
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());
52
- await page.getByRole("button", { name: /submit/i }).click();
53
- const successState = page.getByText(/thank you|success|confirmation/i);
56
+ await page.locator('button[type="submit"], input[type="submit"]').first().click();
57
+ const successState = page.getByText(/thank you|success|confirmation/i).first();
54
58
  await (0, test_1.expect)(successState, "Form should submit successfully after trimming leading/trailing spaces around the email").toBeVisible();
55
59
  });
56
60
  });
@@ -21,9 +21,7 @@ fixtures_1.test.describe("Forms - Invalid Input", () => {
21
21
  // Fill invalid value in the field under test
22
22
  await fieldLocator.first().fill(invalidCase.invalidValue);
23
23
  // Attempt submit
24
- const submitButton = form.getByRole("button", {
25
- name: /(schedule discovery call|submit|send|book|request|get in touch)/i,
26
- });
24
+ const submitButton = form.locator('button[type="submit"], input[type="submit"]');
27
25
  await submitButton.click();
28
26
  // Assert rejection - confirmation should NOT appear
29
27
  const confirmation = page.getByText(/thank you|success|confirmation/i);
@@ -18,12 +18,19 @@ fixtures_1.test.describe("Forms", () => {
18
18
  if (!isRequired && !isEmailField) {
19
19
  fixtures_1.test.skip(true, `${targetField.label.source} field is not marked required on this page`);
20
20
  }
21
- await (0, utils_1.fillFormFields)(page);
22
- await fieldLocator.first().clear();
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);
23
26
  const submitButtons = page.locator('button[type="submit"], input[type="submit"]');
24
27
  await submitButtons.first().click();
25
- const isErrorPresent = await (0, utils_1.hasVisibleErrorNearField)(fieldLocator.first());
26
- (0, test_1.expect)(isErrorPresent, `No visible inline error next to the empty ${targetField.label.source} field after submit`).toBe(true);
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 });
27
34
  const genericError = page.getByText(/form error/i);
28
35
  const genericErrorCount = await genericError.count();
29
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);
@@ -6,9 +6,16 @@ const test_1 = require("@playwright/test");
6
6
  fixtures_1.test.describe("Forms - Thank You Confirm", () => {
7
7
  (0, fixtures_1.test)("FM-07 Confirmation state matches spec and does not resubmit on Back/Forward", async ({ page, targetPage }) => {
8
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
+ }
9
13
  await (0, utils_1.fillFormFields)(page);
10
- await page.getByRole("button", { name: /submit/i }).click();
11
- const confirmation = page.getByText(/thank you|success|confirmation/i);
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();
12
19
  await (0, test_1.expect)(confirmation, "Confirmation message/page did not appear or did not match the design spec").toBeVisible({ timeout: 10_000 });
13
20
  let hasResubmitDialog = false;
14
21
  page.on("dialog", (dialog) => {
@@ -17,8 +24,11 @@ fixtures_1.test.describe("Forms - Thank You Confirm", () => {
17
24
  });
18
25
  await page.goBack();
19
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.
20
31
  await page.goForward();
21
- await (0, test_1.expect)(confirmation, "Confirmation should still be shown after pressing Forward").toBeVisible();
22
32
  (0, test_1.expect)(hasResubmitDialog, 'Browser Forward should NOT show a "resubmit form?" dialog').toBe(false);
23
33
  });
24
34
  });
@@ -3,13 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const fixtures_1 = require("../../fixtures");
4
4
  const test_1 = require("@playwright/test");
5
5
  fixtures_1.test.describe("Landing Page Automated Smoke Suite", () => {
6
- (0, fixtures_1.test)("LP-01: Page returns 200, renders H1, and has no console errors", async ({ page, consoleErrors, targetPage, }) => {
6
+ (0, fixtures_1.test)("LP-01: Page returns 200, renders H1, and has no console errors", async ({ page, consoleErrors, targetPage, }, testInfo) => {
7
7
  let failedNetworkRequests = 0;
8
8
  page.on("response", (response) => {
9
9
  const status = response.status();
10
- // Track any 4xx or 5xx responses (except for the 404 test itself, but this is LP-01)
11
10
  if (status >= 400) {
12
- failedNetworkRequests += 1;
11
+ // A service worker registered in production but not built/served by a dev server is a
12
+ // common, expected gap across apps using this package — surface it as a warning rather
13
+ // than failing the test, since it doesn't indicate a real broken page.
14
+ //
15
+ // Observed directly while diagnosing this check: headless
16
+ // WebKit actually issues the navigator.serviceWorker.register() fetch and gets a real
17
+ // 404 for a missing file, while headless Chromium/Firefox never issue that fetch at
18
+ // all — so the same missing file only ever surfaces as a failed request under WebKit.
19
+ const isServiceWorkerRequest = response.url().includes("service-worker.js");
20
+ if (isServiceWorkerRequest) {
21
+ testInfo.annotations.push({
22
+ type: "warning",
23
+ description: `Service worker registration failed (${String(status)}) — likely not built by the dev server: ${response.url()}`,
24
+ });
25
+ }
26
+ else {
27
+ failedNetworkRequests += 1;
28
+ }
13
29
  }
14
30
  });
15
31
  const response = await page.goto(targetPage);
@@ -295,7 +295,7 @@ function isCutoffAtViewportEdge(snapshot, viewport) {
295
295
  rect.bottom > viewport.height + exports.ResponsiveConfig.CutoffTolerancePX);
296
296
  return hasDimensions && (isHorizontallyClipped || isVerticallyClipped);
297
297
  }
298
- async function findElementsCutoffAtViewportEdge(page, containerSelector = "body *") {
298
+ async function findElementsCutoffAtViewportEdge(page, containerSelector) {
299
299
  const { elements, viewport } = await page.evaluate((selector) => {
300
300
  function isRenderedElement(element) {
301
301
  const style = window.getComputedStyle(element);
@@ -328,7 +328,7 @@ async function findElementsCutoffAtViewportEdge(page, containerSelector = "body
328
328
  }
329
329
  return isClipped;
330
330
  }
331
- const renderedElements = Array.from(document.querySelectorAll(selector))
331
+ const renderedElements = Array.from(document.querySelectorAll(selector ?? "body *"))
332
332
  .filter(isRenderedElement)
333
333
  .filter((element) => {
334
334
  return !isClippedByAncestorOverflow(element);
@@ -462,12 +462,13 @@ exports.FieldValueMappings = [
462
462
  { label: /date/i, validValue: exports.ValidUserInfo.ValidTestPreferredDate, interaction: exports.FieldInteractions.Datetime },
463
463
  { label: /time/i, validValue: exports.ValidUserInfo.ValidTestPreferredTime, interaction: exports.FieldInteractions.Datetime },
464
464
  ];
465
- // Some date/time fields are plain inputs (a direct .fill() works); others are picker widgets that
466
- // open a popup and reject typed text. This tries the plain-input path first, and only falls back
467
- // to interacting with a popup if the value didn't actually take. The fallback uses WAI-ARIA roles
468
- // (grid/gridcell for calendar days, spinbutton for numeric steppers) rather than any specific UI
469
- // library's class names, since this package runs against apps that don't all use the same one —
470
- // it degrades to a no-op if neither the plain-input path nor these ARIA patterns match anything.
465
+ // Some date/time fields are plain inputs; others are picker widgets that open a popup and
466
+ // reject typed text. Tries .fill() first, falling back to ARIA roles (grid/gridcell, spinbutton)
467
+ // if the value didn't take, since apps vary in UI library. No-ops if nothing matches.
468
+ //
469
+ // Mantine's DateTimePicker doesn't use role=gridcell for its calendar days (just plain <button>
470
+ // elements labeled by day number), so the day selector also falls back to any enabled button
471
+ // with a 1-2 digit label.
471
472
  async function fillDateTimePicker(locator, fallbackValue) {
472
473
  await locator.fill(fallbackValue).catch(() => {
473
474
  return undefined;
@@ -478,7 +479,19 @@ async function fillDateTimePicker(locator, fallbackValue) {
478
479
  if (filledValue.length === 0) {
479
480
  await locator.click();
480
481
  const page = locator.page();
481
- const dayCells = page.locator('[role="gridcell"]:not([aria-disabled="true"])');
482
+ // Give the popup a moment to finish laying out before interacting — clicking immediately
483
+ // can hit a day cell while another control (e.g. an AM/PM toggle) is still animating into
484
+ // place on top of it, intercepting the click.
485
+ await page.waitForTimeout(300);
486
+ // Scope the numeric-button fallback to an open popup/dialog when one exists, rather than the
487
+ // whole page, so it can't accidentally match an unrelated numeric button elsewhere (e.g. a
488
+ // pagination control or step counter).
489
+ const popup = page.locator('[role="dialog"], [role="listbox"], [aria-modal="true"]').first();
490
+ const hasPopup = (await popup.count()) > 0;
491
+ const numericDayButtons = (hasPopup ? popup : page)
492
+ .locator("button:not([disabled]):not([aria-disabled='true'])")
493
+ .filter({ hasText: /^\d{1,2}$/ });
494
+ const dayCells = page.locator('[role="gridcell"]:not([aria-disabled="true"])').or(numericDayButtons);
482
495
  const dayCellCount = await dayCells.count();
483
496
  for (let index = 0; index < dayCellCount; index++) {
484
497
  const cell = dayCells.nth(index);
@@ -519,7 +532,10 @@ async function fillDateTimePicker(locator, fallbackValue) {
519
532
  }
520
533
  }
521
534
  async function fillFormFields(page) {
522
- await Promise.all(exports.FieldValueMappings.map(async (field) => {
535
+ // Filled sequentially, not concurrently — see the same note in fillOtherRequiredFieldsValidly:
536
+ // a popup-based widget open in one field can lose focus and close mid-interaction if another
537
+ // field is filled at the same time.
538
+ for (const field of exports.FieldValueMappings) {
523
539
  const locator = page.getByLabel(field.label);
524
540
  if ((await locator.count()) > 0) {
525
541
  if (field.interaction === exports.FieldInteractions.Datetime) {
@@ -529,7 +545,7 @@ async function fillFormFields(page) {
529
545
  await locator.first().fill(field.validValue);
530
546
  }
531
547
  }
532
- }));
548
+ }
533
549
  }
534
550
  async function hasVisibleErrorNearField(fieldLocator) {
535
551
  const fieldID = await fieldLocator.getAttribute("id");
@@ -560,16 +576,35 @@ exports.InvalidInputCases = [
560
576
  { fieldLabel: /phone/i, invalidValue: "12", expectedErrorText: /enter a valid phone number/i },
561
577
  ];
562
578
  async function fillOtherRequiredFieldsValidly(page, skipLabel) {
563
- await Promise.all(exports.FieldValueMappings.map(async (field) => {
579
+ const skipLocator = page.getByLabel(skipLabel);
580
+ const skipFieldCount = await skipLocator.count();
581
+ // Some combined widgets (e.g. a single "Preferred Time & Date" picker) match more than one
582
+ // FieldValueMappings label at once — comparing by element id, not just label text, catches
583
+ // that case so filling the "other" field doesn't silently fill the one under test too.
584
+ const skipFieldID = skipFieldCount > 0 ? await skipLocator.first().getAttribute("id") : null;
585
+ // Filled sequentially, not concurrently: a popup-based widget (like a date/time picker) opens
586
+ // on focus and closes on blur, so filling a different field at the same time can steal focus
587
+ // and close it mid-interaction, detaching whatever this was about to click inside it.
588
+ for (const field of exports.FieldValueMappings) {
564
589
  const isSkippedField = field.label.source === skipLabel.source;
565
590
  const locator = page.getByLabel(field.label);
566
591
  let isFieldPresent = false;
567
592
  if (!isSkippedField) {
568
593
  isFieldPresent = (await locator.count()) > 0;
569
594
  }
595
+ let isSameElementAsSkipped = false;
596
+ if (isFieldPresent && skipFieldID !== null) {
597
+ const fieldID = await locator.first().getAttribute("id");
598
+ isSameElementAsSkipped = fieldID !== null && fieldID === skipFieldID;
599
+ }
600
+ // Email is always treated as required (see the FieldValueMappings doc comment above),
601
+ // regardless of whether it carries a native `required`/`aria-required` attribute — the
602
+ // same rule the isEmailField check in required-field.spec.ts applies for its own skip
603
+ // decision, so filling here needs to agree with it or both can end up empty at once.
604
+ const isEmailField = field.label.source === "email";
570
605
  let isRequired = false;
571
- if (isFieldPresent) {
572
- isRequired = await isFieldRequired(locator.first());
606
+ if (isFieldPresent && !isSameElementAsSkipped) {
607
+ isRequired = isEmailField || (await isFieldRequired(locator.first()));
573
608
  }
574
609
  if (isRequired) {
575
610
  if (field.interaction === exports.FieldInteractions.Datetime) {
@@ -579,7 +614,7 @@ async function fillOtherRequiredFieldsValidly(page, skipLabel) {
579
614
  await locator.first().fill(field.validValue);
580
615
  }
581
616
  }
582
- }));
617
+ }
583
618
  }
584
619
  exports.AutofillFields = [
585
620
  { label: /name/i, expectedAutocomplete: /name/i },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manufac/general-tests",
3
- "version": "0.0.2",
3
+ "version": "0.1.1",
4
4
  "description": "A library for general tests for landing pages",
5
5
  "repository": {
6
6
  "type": "git",