skeptic-cli 0.2.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.
@@ -0,0 +1,20 @@
1
+ // Example skeptic test. See https://github.com/iamjr15/skeptic for the full API.
2
+ import { test, expect } from "skeptic-cli";
3
+
4
+ test("homepage smoke", async ({ page, snapshot, observability, screenshot }) => {
5
+ await page.goto("https://example.com");
6
+
7
+ // Standard Playwright assertions are re-exported.
8
+ await expect(page).toHaveTitle(/Example Domain/);
9
+
10
+ // Snapshot returns an ARIA-tree primitive with ref-based locator helpers —
11
+ // the same pattern AI agents use to discover element shapes.
12
+ const tree = await snapshot(page);
13
+ await expect(tree.byRole("heading", { name: "Example Domain" })).toBeVisible();
14
+
15
+ // Observability assertions are opt-in via `--observability` or test.use.
16
+ // Uncomment when you have collectors attached:
17
+ // await observability.expectPerformance({ lcp: "<2500ms", cls: "<0.1" });
18
+
19
+ await screenshot("homepage", { fullPage: true });
20
+ });
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: accessibility
3
+ description: WCAG-adjacent quality checks — visible focus, hit targets, keyboard reachability, ARIA hygiene.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Accessibility Testing Guidance
8
+
9
+ For automated WCAG checks during a test run, use `observability.expectAccessible(...)` — it runs axe-core and, when available, IBM Equal Access against the live page and surfaces violations as test failures. See the README's Observability section.
10
+
11
+ Use the checks below alongside `ai.assertNoDefects()` or `ai.assert(...)` when your test touches interactive surfaces.
12
+
13
+ ## Things worth asserting
14
+
15
+ - [ ] Every interactive target is at least 44×44 px (tap) or 24×24 css-px with 8px spacing (pointer).
16
+ - [ ] `aria-label` or visible text exists on every `button`, `a`, `[role="button"]` the test touches.
17
+ - [ ] Focus is visible after Tab: assert `:focus-visible` styles apply, not just that `document.activeElement` changed.
18
+ - [ ] Forms have a `<label>` (or `aria-label`) wired to each input.
19
+ - [ ] Error messages live in `[role="alert"]` or `aria-live="polite"` so screen readers announce them.
20
+ - [ ] Modal traps focus inside itself — Tab from the last focusable element lands on the first, not outside.
21
+
22
+ ## Test patterns
23
+
24
+ Keyboard-only reachability:
25
+
26
+ ```ts
27
+ await page.keyboard.press("Tab");
28
+ await expect(page.locator("[data-testid=cta]:focus-visible")).toBeVisible();
29
+ await page.keyboard.press("Enter");
30
+ await expect(page.getByRole("dialog")).toBeVisible();
31
+ ```
32
+
33
+ Avoid `locator.click()` on controls meant to be keyboard-activated — use
34
+ `page.keyboard.press("Enter")` after focus so broken keyboard handlers do not pass.
35
+
36
+ ## Red flags — file a bug
37
+
38
+ - An interactive element with `role="button"` but no `tabindex` (not keyboard-reachable).
39
+ - `aria-hidden="true"` on a container that holds focusable children (screen readers announce nothing, keyboard still reaches them → inconsistent).
40
+ - Focus lands on `<body>` after a modal closes — means the opener wasn't re-focused.
41
+ - Inputs without visible labels, using placeholder-as-label (disappears on focus, poor for AT).
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: animation
3
+ description: Timing, transitions, and reduced-motion checks to make animated UI provable in E2E tests.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Animation Testing Guidance
8
+
9
+ Animations are where flaky E2E tests breed. Assert what's stable, not what's moving.
10
+
11
+ ## Things worth asserting
12
+
13
+ - [ ] Element reaches its final state within 500ms of the trigger. Use `expect(locator).toBeVisible({ timeout })`, not a fixed sleep.
14
+ - [ ] Transitions respect `prefers-reduced-motion`: if the test environment sets it, motion duration collapses to ≤10ms.
15
+ - [ ] Enter animations complete before the next interaction is allowed (no click-through on fade-in modals).
16
+ - [ ] Exit animations don't leave ghost elements behind — assert `expect(locator).toBeHidden()` after dismissal, not just "clicked the X".
17
+
18
+ ## Test patterns
19
+
20
+ Wait for the final state, not the start:
21
+
22
+ ```ts
23
+ await page.getByTestId("open-modal").click();
24
+ await expect(page.locator("[data-testid=modal]:not(.entering)")).toBeVisible({ timeout: 500 });
25
+ await expect(page.getByTestId("modal-title")).toBeVisible();
26
+ ```
27
+
28
+ For reduced-motion suites, set the media query through Playwright emulation and assert the "no motion" path. Do not use `page.waitForTimeout(50)` to approximate a fast animation.
29
+
30
+ ## Red flags — file a bug
31
+
32
+ - Any animation longer than 400ms on a user-initiated interaction (Doherty threshold).
33
+ - Elements with a visible transition but no `transition-property` — implies JS-driven animation; harder to interrupt cleanly on navigation.
34
+ - `pointer-events: none` lingering after an animation ends — blocks clicks on what looks interactive.
35
+ - Missing `@media (prefers-reduced-motion: reduce)` block at all.
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: design
3
+ description: Visual correctness — layout integrity, typography, and spacing claims provable in E2E.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Design Testing Guidance
8
+
9
+ E2E is the right place for layout-integrity checks; it's the wrong place for pixel-perfect screenshot diffing of every page.
10
+
11
+ ## Things worth asserting
12
+
13
+ - [ ] Primary CTA is visible without scroll on the target viewport (use `test.use({ device })` or CLI `--device` plus `expect(locator).toBeVisible()`).
14
+ - [ ] Text doesn't overflow its container — screen-dimension edge cases on long strings (German, tokens with no break points).
15
+ - [ ] Z-index ordering holds: modal above page chrome, toast above modal, dropdown above sticky header.
16
+ - [ ] Hover / active / focus states don't collapse the layout (no shift on interaction).
17
+
18
+ ## Test patterns
19
+
20
+ Visual regression on a key component, not the whole page:
21
+
22
+ ```ts
23
+ await page.goto("/checkout");
24
+ await screenshot("checkout-cta", {
25
+ annotate: true,
26
+ annotateScope: "[data-testid=primary-cta]",
27
+ });
28
+ ```
29
+
30
+ For text-overflow checks, use `ai.assert(...)` with a specific prompt — deterministic DOM assertions miss rendered ellipsis/clipping:
31
+
32
+ ```ts
33
+ await ai.assert("The product title is fully visible with no ellipsis truncation inside its card.");
34
+ ```
35
+
36
+ ## Red flags — file a bug
37
+
38
+ - Horizontal scrollbars on the target viewport (any form of `overflow-x: scroll` on body or main).
39
+ - Touch targets within 8px of each other on a mobile profile.
40
+ - Elements with `display: none` but still taking DOM space during measurement — suggests reflow timing issue.
41
+ - Color combinations where contrast ratio falls below 4.5:1 for body text (flag for a11y review too).
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: performance
3
+ description: Latency budgets, observable jank, and network-boundary checks for E2E runs.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Performance Testing Guidance
8
+
9
+ For threshold assertions on Core Web Vitals (FCP, LCP, CLS, INP, TTFB) during a test run, use `observability.expectPerformance(...)`. It captures `web-vitals` metrics from the page and asserts against your thresholds. See the README's Observability section.
10
+
11
+ Performance assertions belong in E2E only when they check user-observable thresholds, not lab metrics.
12
+
13
+ ## Things worth asserting
14
+
15
+ - [ ] Interaction-to-Next-Paint (INP) is under 200ms for primary actions. Capture via `observability.expectPerformance({ inp: "<200ms" })` when the browser reports it.
16
+ - [ ] Response to input within 400ms (Doherty threshold) — extend action timeout only when the test genuinely awaits work, not to mask slowness.
17
+ - [ ] Page-level waits don't exceed 5s in staging with warm cache. Longer means you're testing something other than the UI.
18
+ - [ ] Time-to-interactive hooks exist: assert a known element becomes clickable, not just that DOM loaded.
19
+
20
+ ## Test patterns
21
+
22
+ Measure, don't estimate:
23
+
24
+ ```ts
25
+ await page.goto("/dashboard");
26
+ await observability.expectPerformance({ lcp: "<2500ms", cls: "<0.1" });
27
+ await ai.assert("The dashboard's first meaningful content is visible, not a skeleton loader.");
28
+ ```
29
+
30
+ For flaky network-bound tests, prefer `--retries <n>` or focused waiting on the resulting UI over bumping timeouts indefinitely.
31
+
32
+ ## Red flags — file a bug
33
+
34
+ - Any step requiring `timeout: 30000+` for a staging environment — indicates a real regression, not a flaky test.
35
+ - Waiting on a spinner element instead of the downstream content that's supposed to appear.
36
+ - `page.waitForTimeout(...)` as a synchronization primitive. Convert to `expect(locator).toBeVisible()` or another state-based wait.
37
+ - Layout shift during the first 2.5s after navigate (assert via an AI step that reports visible CLS-adjacent movement).
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: react
3
+ description: React-specific pitfalls observable from E2E — hydration mismatches, key collisions, stale effects.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # React Testing Guidance
8
+
9
+ E2E sees React as a black box, but a handful of pathologies show up as user-visible bugs that deterministic DOM assertions miss.
10
+
11
+ ## Things worth asserting
12
+
13
+ - [ ] No hydration mismatches: after `page.goto`, the first pass should not visibly flicker between two DOMs. Use targeted screenshots around the hero area before any interaction.
14
+ - [ ] Keys in lists are stable across re-renders — add a test that triggers a filter change and assert items retain their expected state (checked checkbox, focused input).
15
+ - [ ] Suspense fallbacks disappear once data arrives, AND don't flash when navigating between already-cached routes.
16
+ - [ ] Errors caught by an error boundary render a specific fallback UI, not a blank screen.
17
+
18
+ ## Test patterns
19
+
20
+ Check that state survives a re-render:
21
+
22
+ ```ts
23
+ await page.getByTestId("search").fill("foo");
24
+ await page.getByTestId("filter-active").click();
25
+ await expect(page.getByTestId("search")).toHaveValue("foo");
26
+ ```
27
+
28
+ Verify an error boundary:
29
+
30
+ ```ts
31
+ await page.getByTestId("trigger-error").click();
32
+ await expect(page.getByTestId("error-boundary-fallback")).toBeVisible();
33
+ await expect(page.getByTestId("raw-error-screen")).toBeHidden();
34
+ ```
35
+
36
+ For route-transition bugs (stale effects persisting), navigate away and back, then assert no duplicate elements:
37
+
38
+ ```ts
39
+ await page.goto("/profile");
40
+ await page.goto("/home");
41
+ await page.goto("/profile");
42
+ await expect(page.getByTestId("profile-header")).toHaveCount(1);
43
+ ```
44
+
45
+ ## Red flags — file a bug
46
+
47
+ - Any `setState` visible as a two-frame flicker (render → microtask → render) — usually a missing `useMemo` or a stale closure.
48
+ - Controlled inputs that lag by one keystroke (state-update bug or ref mismatch).
49
+ - `key={index}` on lists that can reorder — unexplained UI state corruption after filter changes.
50
+ - Portal-rendered content that survives its parent unmount (leaked modal).
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: responsive
3
+ description: Viewport adaptations, breakpoint behavior, and orientation correctness.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Responsive Testing Guidance
8
+
9
+ Skeptic device profiles let you emulate specific viewports. Use `test.use({ device })` or CLI `--device` plus targeted screenshots for responsive-layout claims; don't try to test every breakpoint in one test.
10
+
11
+ ## Things worth asserting
12
+
13
+ - [ ] Primary navigation collapses to a menu button below the mobile breakpoint — assert both the button is visible and the full-nav list is NOT.
14
+ - [ ] Tap targets meet 44×44 px on mobile profiles even if they're smaller on desktop.
15
+ - [ ] Content doesn't require horizontal scroll at 320px wide (smallest practical mobile).
16
+ - [ ] Images use `srcset` / `<picture>` and the correct variant loads for the emulated DPR.
17
+ - [ ] Sticky headers respect `safe-area-inset-top` on notched devices (assert via `evalScript` reading the computed style).
18
+
19
+ ## Test patterns
20
+
21
+ One test per critical breakpoint:
22
+
23
+ ```ts
24
+ test("nav on mobile", async ({ page }) => {
25
+ await page.goto("/");
26
+ await expect(page.getByTestId("hamburger")).toBeVisible();
27
+ await expect(page.getByTestId("desktop-nav")).toBeHidden();
28
+ await page.getByTestId("hamburger").click();
29
+ await expect(page.getByTestId("mobile-drawer")).toBeVisible();
30
+ }, { device: "iphone_16" });
31
+ ```
32
+
33
+ Orientation changes should assert layout before and after:
34
+
35
+ ```ts
36
+ await screenshot("orientation-portrait", { fullPage: true });
37
+ await page.setViewportSize({ width: 844, height: 390 });
38
+ await screenshot("orientation-landscape", { fullPage: true });
39
+ ```
40
+
41
+ ## Red flags — file a bug
42
+
43
+ - Any element that sits outside the viewport at 320px after normal scroll.
44
+ - Content that overflows with `overflow: visible` + a wrapper with `overflow: hidden` — fragile clipping.
45
+ - Font sizes that don't scale via `clamp()` / viewport units between breakpoints — pixel-fixed typography reads as "broken" on small screens.
46
+ - Interactions that depend on hover state without a fallback (mobile has no hover).
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: security
3
+ description: Client-side security posture — secrets exposure, XSS surface, auth state assertions.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # Security Testing Guidance
8
+
9
+ E2E isn't a substitute for a proper security audit, but it's the last line of defense against silent regressions that a scanner wouldn't catch.
10
+
11
+ ## Things worth asserting
12
+
13
+ - [ ] Auth tokens never appear in `document.cookie` without `HttpOnly`. Use `page.evaluate` to read cookies — anything that reads as a bearer should be flagged.
14
+ - [ ] Error pages don't leak stack traces, env vars, or internal paths. Use `ai.assertNoDefects()` or `ai.assert(...)` with a prompt that explicitly flags "stack trace visible to user".
15
+ - [ ] Logged-out state flushes client-side state: after clicking Logout, assert every protected route redirects to sign-in.
16
+ - [ ] Forms with sensitive input have `autocomplete` set appropriately (`off` for OTP/2FA, `current-password` for login, `new-password` for signup).
17
+
18
+ ## Test patterns
19
+
20
+ Verify sanitization on user-provided content:
21
+
22
+ ```ts
23
+ await page.getByTestId("comment-input").fill("<script>window.__xss__=true</script>");
24
+ await page.getByRole("button", { name: "Submit" }).click();
25
+ await expect(page.getByTestId("comment")).toBeVisible();
26
+ await expect(page.evaluate(() => (window as typeof window & { __xss__?: boolean }).__xss__)).resolves.toBeUndefined();
27
+ ```
28
+
29
+ Check that a copy-to-clipboard of a token is the token, not a placeholder:
30
+
31
+ ```ts
32
+ await page.getByTestId("copy-api-key").click();
33
+ const key = await page.getByTestId("api-key-display").innerText();
34
+ expect(key).toMatch(/^sk_[A-Za-z0-9_]+$/);
35
+ ```
36
+
37
+ ## Red flags — file a bug
38
+
39
+ - API responses visible in DevTools network tab that embed PII in URLs (should be request body + HTTPS).
40
+ - `dangerouslySetInnerHTML` on user-controlled strings (React) — E2E cannot prove absence; add a unit test.
41
+ - Hash fragment containing session state after login (bookmarkable session).
42
+ - Third-party script loading before consent in EU locale profiles.
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: seo
3
+ description: Metadata, crawlability, and rendering fidelity for search-visible pages.
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # SEO Testing Guidance
8
+
9
+ E2E catches the SEO regressions that static linting won't: post-hydration DOM shape, canonical mismatches, client-only content.
10
+
11
+ ## Things worth asserting
12
+
13
+ - [ ] `<title>` is non-empty and page-specific (not "React App" or the framework default).
14
+ - [ ] Exactly one `<link rel="canonical">` points to the URL the page should be indexed at.
15
+ - [ ] `<meta name="description">` exists and is 50–160 characters.
16
+ - [ ] Open Graph + Twitter Card tags present on public pages: `og:title`, `og:image`, `og:url`, `twitter:card`.
17
+ - [ ] Structured data validates — presence of at least the expected `@type` for the page category (Product / Article / Organization).
18
+
19
+ ## Test patterns
20
+
21
+ Assert metadata after navigation:
22
+
23
+ ```ts
24
+ await page.goto("/products/abc");
25
+ const metadata = await page.evaluate(() => ({
26
+ canonical: document.querySelector<HTMLLinkElement>('link[rel=canonical]')?.href,
27
+ title: document.title,
28
+ ogTitle: document.querySelector<HTMLMetaElement>('meta[property="og:title"]')?.content,
29
+ }));
30
+ expect(metadata.title).toMatch(/abc/i);
31
+ expect(metadata.canonical).toContain("/products/abc");
32
+ await ai.assert("The page metadata refers to a specific product, not a generic catch-all.");
33
+ ```
34
+
35
+ For SSR vs hydrated rendering parity, flag any element that exists at initial HTML but gets replaced on hydration. Capture initial page content before interaction, then compare against the hydrated DOM.
36
+
37
+ ## Red flags — file a bug
38
+
39
+ - Client-rendered content inside tags that bots read (title / meta / structured data) — JS-dependent SEO is fragile.
40
+ - Multiple H1s on a page, or H1 that depends on carousel state.
41
+ - Canonical pointing to itself when it should point to a cleaner URL (losing rank signals).
42
+ - `robots` meta set to `noindex` on pages that should be indexed (staging config leaking to prod).
43
+ - Duplicate content between `/path` and `/path/` without a canonical.
@@ -0,0 +1,31 @@
1
+ # skeptic configuration
2
+ # Docs: https://skeptic.dev/docs/config
3
+
4
+ # Base URL for all tests (override per-test with `test.use({ url: ... })`)
5
+ # url: http://localhost:3000
6
+
7
+ # Glob patterns for test files
8
+ tests: "tests/**/*.spec.ts"
9
+
10
+ browser:
11
+ engine: chromium
12
+ headless: true
13
+ timeout: 30000
14
+ viewport:
15
+ width: 1280
16
+ height: 720
17
+
18
+ auth:
19
+ cookies: false
20
+
21
+ execution:
22
+ retries: 0
23
+ bail: false
24
+ screenshotOnFailure: true
25
+
26
+ output:
27
+ dir: ./skeptic-output
28
+ reporters:
29
+ - console
30
+
31
+ env: {}
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "resolveJsonModule": true,
10
+ "allowImportingTsExtensions": false,
11
+ "noEmit": true,
12
+ "types": ["node"]
13
+ },
14
+ "include": ["tests/**/*.ts", "tests/**/*.tsx"]
15
+ }
@@ -0,0 +1 @@
1
+ var webVitals=function(e){"use strict";var n,t,r,i,o,a=-1,c=function(e){addEventListener("pageshow",(function(n){n.persisted&&(a=n.timeStamp,e(n))}),!0)},u=function(){var e=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},s=function(){var e=u();return e&&e.activationStart||0},f=function(e,n){var t=u(),r="navigate";a>=0?r="back-forward-cache":t&&(document.prerendering||s()>0?r="prerender":document.wasDiscarded?r="restore":t.type&&(r=t.type.replace(/_/g,"-")));return{name:e,value:void 0===n?-1:n,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r}},d=function(e,n,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){var r=new PerformanceObserver((function(e){Promise.resolve().then((function(){n(e.getEntries())}))}));return r.observe(Object.assign({type:e,buffered:!0},t||{})),r}}catch(e){}},l=function(e,n,t,r){var i,o;return function(a){n.value>=0&&(a||r)&&((o=n.value-(i||0))||void 0===i)&&(i=n.value,n.delta=o,n.rating=function(e,n){return e>n[1]?"poor":e>n[0]?"needs-improvement":"good"}(n.value,t),e(n))}},p=function(e){requestAnimationFrame((function(){return requestAnimationFrame((function(){return e()}))}))},v=function(e){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&e()}))},m=function(e){var n=!1;return function(){n||(e(),n=!0)}},h=-1,g=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},T=function(e){"hidden"===document.visibilityState&&h>-1&&(h="visibilitychange"===e.type?e.timeStamp:0,E())},y=function(){addEventListener("visibilitychange",T,!0),addEventListener("prerenderingchange",T,!0)},E=function(){removeEventListener("visibilitychange",T,!0),removeEventListener("prerenderingchange",T,!0)},C=function(){return h<0&&(h=g(),y(),c((function(){setTimeout((function(){h=g(),y()}),0)}))),{get firstHiddenTime(){return h}}},L=function(e){document.prerendering?addEventListener("prerenderingchange",(function(){return e()}),!0):e()},S=[1800,3e3],b=function(e,n){n=n||{},L((function(){var t,r=C(),i=f("FCP"),o=d("paint",(function(e){e.forEach((function(e){"first-contentful-paint"===e.name&&(o.disconnect(),e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-s(),0),i.entries.push(e),t(!0)))}))}));o&&(t=l(e,i,S,n.reportAllChanges),c((function(r){i=f("FCP"),t=l(e,i,S,n.reportAllChanges),p((function(){i.value=performance.now()-r.timeStamp,t(!0)}))})))}))},w=[.1,.25],I=0,P=1/0,A=0,F=function(e){e.forEach((function(e){e.interactionId&&(P=Math.min(P,e.interactionId),A=Math.max(A,e.interactionId),I=A?(A-P)/7+1:0)}))},M=function(){return n?I:performance.interactionCount||0},k=function(){"interactionCount"in performance||n||(n=d("event",F,{type:"event",buffered:!0,durationThreshold:0}))},D=[],B=new Map,R=0,x=function(){var e=Math.min(D.length-1,Math.floor((M()-R)/50));return D[e]},H=[],N=function(e){if(H.forEach((function(n){return n(e)})),e.interactionId||"first-input"===e.entryType){var n=D[D.length-1],t=B.get(e.interactionId);if(t||D.length<10||e.duration>n.latency){if(t)e.duration>t.latency?(t.entries=[e],t.latency=e.duration):e.duration===t.latency&&e.startTime===t.entries[0].startTime&&t.entries.push(e);else{var r={id:e.interactionId,latency:e.duration,entries:[e]};B.set(r.id,r),D.push(r)}D.sort((function(e,n){return n.latency-e.latency})),D.length>10&&D.splice(10).forEach((function(e){return B.delete(e.id)}))}}},q=function(e){var n=self.requestIdleCallback||self.setTimeout,t=-1;return e=m(e),"hidden"===document.visibilityState?e():(t=n(e),v(e)),t},O=[200,500],j=[2500,4e3],V={},_=[800,1800],z=function e(n){document.prerendering?L((function(){return e(n)})):"complete"!==document.readyState?addEventListener("load",(function(){return e(n)}),!0):setTimeout(n,0)},G={passive:!0,capture:!0},J=new Date,K=function(e,n){t||(t=n,r=e,i=new Date,W(removeEventListener),Q())},Q=function(){if(r>=0&&r<i-J){var e={entryType:"first-input",name:t.type,target:t.target,cancelable:t.cancelable,startTime:t.timeStamp,processingStart:t.timeStamp+r};o.forEach((function(n){n(e)})),o=[]}},U=function(e){if(e.cancelable){var n=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,n){var t=function(){K(e,n),i()},r=function(){i()},i=function(){removeEventListener("pointerup",t,G),removeEventListener("pointercancel",r,G)};addEventListener("pointerup",t,G),addEventListener("pointercancel",r,G)}(n,e):K(n,e)}},W=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return e(n,U,G)}))},X=[100,300];return e.CLSThresholds=w,e.FCPThresholds=S,e.FIDThresholds=X,e.INPThresholds=O,e.LCPThresholds=j,e.TTFBThresholds=_,e.onCLS=function(e,n){n=n||{},b(m((function(){var t,r=f("CLS",0),i=0,o=[],a=function(e){e.forEach((function(e){if(!e.hadRecentInput){var n=o[0],t=o[o.length-1];i&&e.startTime-t.startTime<1e3&&e.startTime-n.startTime<5e3?(i+=e.value,o.push(e)):(i=e.value,o=[e])}})),i>r.value&&(r.value=i,r.entries=o,t())},u=d("layout-shift",a);u&&(t=l(e,r,w,n.reportAllChanges),v((function(){a(u.takeRecords()),t(!0)})),c((function(){i=0,r=f("CLS",0),t=l(e,r,w,n.reportAllChanges),p((function(){return t()}))})),setTimeout(t,0))})))},e.onFCP=b,e.onFID=function(e,n){n=n||{},L((function(){var i,a=C(),u=f("FID"),s=function(e){e.startTime<a.firstHiddenTime&&(u.value=e.processingStart-e.startTime,u.entries.push(e),i(!0))},p=function(e){e.forEach(s)},h=d("first-input",p);i=l(e,u,X,n.reportAllChanges),h&&(v(m((function(){p(h.takeRecords()),h.disconnect()}))),c((function(){var a;u=f("FID"),i=l(e,u,X,n.reportAllChanges),o=[],r=-1,t=null,W(addEventListener),a=s,o.push(a),Q()})))}))},e.onINP=function(e,n){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(n=n||{},L((function(){var t;k();var r,i=f("INP"),o=function(e){q((function(){e.forEach(N);var n=x();n&&n.latency!==i.value&&(i.value=n.latency,i.entries=n.entries,r())}))},a=d("event",o,{durationThreshold:null!==(t=n.durationThreshold)&&void 0!==t?t:40});r=l(e,i,O,n.reportAllChanges),a&&(a.observe({type:"first-input",buffered:!0}),v((function(){o(a.takeRecords()),r(!0)})),c((function(){R=M(),D.length=0,B.clear(),i=f("INP"),r=l(e,i,O,n.reportAllChanges)})))})))},e.onLCP=function(e,n){n=n||{},L((function(){var t,r=C(),i=f("LCP"),o=function(e){n.reportAllChanges||(e=e.slice(-1)),e.forEach((function(e){e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-s(),0),i.entries=[e],t())}))},a=d("largest-contentful-paint",o);if(a){t=l(e,i,j,n.reportAllChanges);var u=m((function(){V[i.id]||(o(a.takeRecords()),a.disconnect(),V[i.id]=!0,t(!0))}));["keydown","click"].forEach((function(e){addEventListener(e,(function(){return q(u)}),{once:!0,capture:!0})})),v(u),c((function(r){i=f("LCP"),t=l(e,i,j,n.reportAllChanges),p((function(){i.value=performance.now()-r.timeStamp,V[i.id]=!0,t(!0)}))}))}}))},e.onTTFB=function(e,n){n=n||{};var t=f("TTFB"),r=l(e,t,_,n.reportAllChanges);z((function(){var i=u();i&&(t.value=Math.max(i.responseStart-s(),0),t.entries=[i],r(!0),c((function(){t=f("TTFB",0),(r=l(e,t,_,n.reportAllChanges))(!0)})))}))},e}({});