├── .gitignore
├── .gitattributes
├── packages
└── astro-umami
│ ├── .gitignore
│ ├── tsconfig.json
│ ├── package.json
│ ├── readme.md
│ └── src
│ └── index.ts
├── readme.md
├── .npmrc
├── playground
├── src
│ ├── env.d.ts
│ └── pages
│ │ └── index.astro
├── .gitignore
├── tsconfig.json
├── package.json
├── playwright.config.ts
├── astro.config.ts
└── tests
│ └── index.spec.ts
├── pnpm-workspace.yaml
├── .editorconfig
├── .github
├── workflows
│ ├── release.yml
│ └── test.yml
└── dependabot.yml
├── package.json
├── license
└── eslint.config.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/packages/astro-umami/.gitignore:
--------------------------------------------------------------------------------
1 | dist
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | packages/astro-umami/readme.md
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | ignore-workspace-root-check = true
2 | save-exact = true
3 |
--------------------------------------------------------------------------------
/playground/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "packages/*"
3 | - playground
4 |
--------------------------------------------------------------------------------
/playground/.gitignore:
--------------------------------------------------------------------------------
1 | .astro
2 | /node_modules/
3 | /playwright-report/
4 | /test-results/
5 |
--------------------------------------------------------------------------------
/playground/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/strict",
3 | "compilerOptions": {
4 | "target": "ESNext",
5 | "jsx": "preserve",
6 | "module": "ESNext"
7 | },
8 | "exclude": [
9 | "dist"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/playground/src/pages/index.astro:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Welcome to Astro!
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | max_line_length = 80
10 | trim_trailing_whitespace = true
11 |
12 | [*.md,*.mdx]
13 | max_line_length = 80
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/packages/astro-umami/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/strictest",
3 | "compilerOptions": {
4 | "target": "es2022",
5 | "lib": [
6 | "es2022"
7 | ],
8 | "module": "ESNext",
9 | "moduleResolution": "Bundler",
10 | "outDir": "dist"
11 | },
12 | "include": [
13 | "src"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | jobs:
9 | release:
10 | permissions:
11 | contents: write
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 0
17 |
18 | - uses: actions/setup-node@v4
19 | with:
20 | node-version: lts/*
21 |
22 | - run: npx changelogithub
23 | env:
24 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
25 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: /
5 | schedule:
6 | interval: weekly
7 | open-pull-requests-limit: 5
8 | labels:
9 | - dependencies
10 | groups:
11 | # Group ESLint dependencies together
12 | eslint:
13 | patterns:
14 | - "@eslint/js"
15 | - "eslint"
16 | # Group Playwright dependencies together
17 | playwright:
18 | patterns:
19 | - "@playwright/test"
20 | - "playwright"
21 |
--------------------------------------------------------------------------------
/playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "scripts": {
7 | "build": "astro check && astro build",
8 | "dev": "astro dev",
9 | "preview": "astro preview",
10 | "start": "astro dev",
11 | "test": "playwright test"
12 | },
13 | "dependencies": {
14 | "@yeskunall/astro-umami": "workspace:*",
15 | "astro": "5.16.6",
16 | "astro-integration-kit": "0.19.1"
17 | },
18 | "devDependencies": {
19 | "@astrojs/check": "0.9.6",
20 | "@playwright/test": "1.57.0",
21 | "playwright": "1.57.0",
22 | "typescript": "5.9.3"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/playground/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import process from "node:process";
2 | import { defineConfig, devices } from "@playwright/test";
3 |
4 | export default defineConfig({
5 | testDir: "tests",
6 | fullyParallel: true,
7 | projects: [
8 | {
9 | name: "chromium",
10 | use: { ...devices["Desktop Chrome"], channel: "chromium" },
11 | },
12 | ],
13 | reporter: "html",
14 | retries: process.env.CI ? 2 : 0,
15 | use: {
16 | baseURL: "http://localhost:4321",
17 | trace: "on-first-retry",
18 | },
19 | webServer: {
20 | command: "pnpm dev",
21 | url: "http://localhost:4321",
22 | reuseExistingServer: !process.env.CI,
23 | },
24 | workers: process.env.CI ? 1 : undefined,
25 | });
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module",
3 | "private": true,
4 | "packageManager": "pnpm@9.15.6",
5 | "license": "MIT",
6 | "engines": {
7 | "node": "^18.20.8 || ^20.3.0 || >=22"
8 | },
9 | "scripts": {
10 | "build": "pnpm --filter=\"./packages/*\" --recursive run build",
11 | "dev": "pnpm --filter=\"./packages/*\" --recursive run dev",
12 | "lint": "eslint --max-warnings 0 .",
13 | "prepublishOnly": "pnpm run build",
14 | "test": "pnpm --filter=\"playground\" --recursive run test"
15 | },
16 | "devDependencies": {
17 | "@eslint/js": "9.39.2",
18 | "@stylistic/eslint-plugin": "5.6.1",
19 | "eslint": "9.39.2",
20 | "jiti": "2.6.1",
21 | "typescript-eslint": "8.50.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/playground/astro.config.ts:
--------------------------------------------------------------------------------
1 | import { createResolver } from "astro-integration-kit";
2 | import { hmrIntegration } from "astro-integration-kit/dev";
3 | import { defineConfig } from "astro/config";
4 |
5 | const { default: packageName } = await import("@yeskunall/astro-umami");
6 |
7 | // https://astro.build/config
8 | export default defineConfig({
9 | integrations: [
10 | packageName({
11 | autotrack: false,
12 | beforeSendHandler: "beforeSendHandler",
13 | doNotTrack: true,
14 | excludeHash: true,
15 | excludeSearch: true,
16 | id: "94db1cb1-74f4-4a40-ad6c-962362670409",
17 | domains: ["example.com", "com.example"],
18 | hostUrl: "https://analytics.eu.umami.is",
19 | tag: "test-tag",
20 | withPartytown: true,
21 | }),
22 | hmrIntegration({
23 | directory: createResolver(import.meta.url).resolve(
24 | "../packages/astro-umami",
25 | ),
26 | }),
27 | ],
28 | });
29 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: E2E tests
2 | on:
3 | pull_request:
4 | branches:
5 | - main
6 | push:
7 | branches:
8 | - main
9 |
10 | permissions:
11 | actions: read
12 | contents: read
13 |
14 | jobs:
15 | test:
16 | name: Node.js ${{ matrix.node-version }}
17 | runs-on: ubuntu-latest
18 | strategy:
19 | fail-fast: false
20 | matrix:
21 | node-version:
22 | - 18
23 | - 20
24 | - 22
25 | - 24
26 | timeout-minutes: 30
27 | defaults:
28 | run:
29 | working-directory: playground
30 | steps:
31 | - uses: actions/checkout@v5
32 | - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
33 | - uses: actions/setup-node@v5
34 | with:
35 | node-version: ${{ matrix.node-version }}
36 | cache: pnpm
37 | - run: |
38 | pnpm install --frozen-lockfile
39 | pnpm exec playwright install --with-deps chromium --no-shell
40 | - run: pnpm build
41 | working-directory: packages/astro-umami
42 | - run: pnpm test
43 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Kunall Banerjee
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/eslint.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "eslint/config";
2 | import js from "@eslint/js";
3 | import stylistic from "@stylistic/eslint-plugin";
4 | import tseslint from "typescript-eslint";
5 |
6 | import type { Linter } from "eslint";
7 |
8 | const stylisticConfig = stylistic.configs.customize({
9 | indent: 2,
10 | quotes: "double",
11 | semi: true,
12 | });
13 |
14 | const stylisticRulesAsWarnings: Record
15 | = Object.entries(stylisticConfig.rules || {}).reduce(
16 | (acc, [ruleName, ruleConfig]) => {
17 | if (Array.isArray(ruleConfig)) {
18 | // Rule has configuration options: ["error", { options }] -> ["warn", { options }]
19 | acc[ruleName] = ["warn", ...ruleConfig.slice(1)];
20 | }
21 | else {
22 | // Rule is just a severity level: "error" -> "warn"
23 | acc[ruleName] = "warn";
24 | }
25 |
26 | return acc;
27 | },
28 | {} as Record,
29 | );
30 |
31 | export default defineConfig(
32 | { ignores: ["**/dist/**"] },
33 | {
34 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
35 | files: ["**/*.{ts,tsx}"],
36 | languageOptions: {
37 | ecmaVersion: "latest",
38 | },
39 | plugins: {
40 | "@stylistic": stylistic,
41 | },
42 | rules: {
43 | ...stylisticRulesAsWarnings,
44 | },
45 | },
46 | );
47 |
--------------------------------------------------------------------------------
/packages/astro-umami/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@yeskunall/astro-umami",
3 | "type": "module",
4 | "version": "0.0.7",
5 | "description": "Add Umami Analytics to your Astro website",
6 | "author": {
7 | "name": "Kunall Banerjee",
8 | "email": "hey@kunall.dev",
9 | "url": "https://kunall.dev"
10 | },
11 | "license": "MIT",
12 | "homepage": "https://github.com/yeskunall/astro-umami#readme",
13 | "repository": "yeskunall/astro-umami",
14 | "bugs": {
15 | "url": "https://github.com/yeskunall/astro-umami#issues"
16 | },
17 | "keywords": [
18 | "alternative",
19 | "analytics",
20 | "astro",
21 | "astro-integration",
22 | "ccpa",
23 | "gdpr",
24 | "pecr",
25 | "privacy",
26 | "replacement",
27 | "umami",
28 | "umami.is",
29 | "umami-analytics",
30 | "withastro"
31 | ],
32 | "exports": {
33 | ".": {
34 | "types": "./dist/index.d.ts",
35 | "default": "./dist/index.js"
36 | }
37 | },
38 | "files": [
39 | "dist"
40 | ],
41 | "scripts": {
42 | "build": "tsup src/index.ts --no-config --external astro-integration-kit --clean --dts --format esm --minify --target node18",
43 | "dev": "tsup src/index.ts --no-config --format esm --clean --watch --target node18"
44 | },
45 | "peerDependencies": {
46 | "astro": "^3.0.0 || ^4.0.0 || ^5.0.0"
47 | },
48 | "devDependencies": {
49 | "tsup": "8.5.1",
50 | "typescript": "5.9.3"
51 | },
52 | "publishConfig": {
53 | "access": "public"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/astro-umami/readme.md:
--------------------------------------------------------------------------------
1 | # astro-umami
2 |
3 | > An [Astro integration](https://docs.astro.build/en/guides/integrations-guide/) to add [Umami Analytics](https://umami.is/) to your website.
4 |
5 | [](https://github.com/yeskunall/astro-umami/actions/workflows/test.yml)
6 | [](https://github.com/yeskunall/astro-umami/blob/main/license)
7 | [](https://badge.fury.io/js/@yeskunall%2Fastro-umami)
8 | 
9 |
10 | ## Highlights
11 |
12 | - Automatically detects if you’re using [View Transitions](https://docs.astro.build/en/guides/view-transitions/) and adds a [`data-astro-rerun`](https://docs.astro.build/en/guides/view-transitions/#data-astro-rerun) attribute
13 | - Disables events and pageviews during development
14 | - Supports all [configuration](https://umami.is/docs/tracker-configuration) options, unlike [`astro-analytics`](https://github.com/Destiner/astro-analytics)
15 | - Provides inline documentation for all configuration options (thanks to [TypeScript](https://github.com/yeskunall/astro-umami/blob/main/packages/astro-umami/src/index.ts#L7))
16 | - (_Optionally_) Serve the tracking script using [Partytown](https://partytown.qwik.dev/) (v0.0.5 onwards)
17 | - **Actively maintained**
18 | - **Zero dependencies**
19 |
20 | ## Usage
21 |
22 | ### Install
23 |
24 | Run the following from your project directory and follow the prompts:
25 |
26 | ```sh
27 | pnpm astro add @yeskunall/astro-umami@0.0.7
28 | ```
29 |
30 | This will install and make the appropriate changes to your Astro config automatically.
31 |
32 | ### Manual install
33 |
34 | 1. Install the required dependencies
35 |
36 | ```sh
37 | pnpm add @yeskunall/astro-umami@0.0.7
38 | ```
39 |
40 | 2. Add the integration to your Astro config:
41 |
42 | ```diff
43 | + import umami from "@yeskunall/astro-umami";
44 |
45 | export default defineConfig({
46 | integrations: [
47 | + umami({ id: "94db1cb1-74f4-4a40-ad6c-962362670409" }),
48 | ],
49 | });
50 | ```
51 |
52 | ###### For all configurable options, see the [interface](https://github.com/yeskunall/astro-umami/blob/main/packages/astro-umami/src/index.ts#L7).
53 |
--------------------------------------------------------------------------------
/packages/astro-umami/src/index.ts:
--------------------------------------------------------------------------------
1 | import type { AstroIntegration } from "astro";
2 |
3 | type OptionalExceptFor = {
4 | [P in keyof T]: P extends K ? T[P] : T[P] | undefined;
5 | } & Pick;
6 |
7 | interface UmamiOptions {
8 | /**
9 | * Umami tracks all events and pageviews for you automatically. Override this behavior if you plan on using [tracker functions](https://umami.is/docs/tracker-functions).
10 | *
11 | @default true
12 | */
13 | autotrack?: boolean;
14 | /**
15 | * Specify a [function](https://umami.is/docs/tracker-configuration#data-before-send) that will be called before data is sent.
16 | */
17 | beforeSendHandler?: string;
18 | /**
19 | * If you want the tracker to only run on specific domains, add them to this list.
20 | *
21 | * @example ["mywebsite.com", "mywebsite2.com"]
22 | */
23 | domains?: string[];
24 | /**
25 | * Respect a visitor’s [Do Not Track](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/doNotTrack) browser setting.
26 | */
27 | doNotTrack?: boolean;
28 | /**
29 | *
30 | * The endpoint where your Umami instance is located.
31 | *
32 | * @default https://cloud.umami.is
33 | * @example https://umami-on.fly.dev
34 | */
35 | endpointUrl?: string;
36 | /**
37 | * Set this if you don’t want to collect the hash value from the URL.
38 | */
39 | excludeHash?: boolean;
40 | /**
41 | * Set this if you don’t want to collect search parameters from the URL.
42 | */
43 | excludeSearch?: boolean;
44 | /**
45 | * Override the location where your analytics data is sent.
46 | */
47 | hostUrl?: string;
48 | /**
49 | * The unique ID of your [website](https://umami.is/docs/add-a-website).
50 | */
51 | id: string;
52 | /**
53 | * Collect events under a specific tag. These events can be filtered in the dashboard by the specific tag.
54 | */
55 | tag?: string;
56 | /**
57 | * Assign a custom name to the tracker script.
58 | *
59 | * @default script.js
60 | * @see [https://umami.is/docs/environment-variables](https://umami.is/docs/environment-variables)
61 | */
62 | trackerScriptName?: string;
63 | }
64 |
65 | interface Options extends UmamiOptions {
66 | /**
67 | * Serve the tracking script using [Partytown](https://partytown.qwik.dev/).
68 | *
69 | * @see [https://docs.astro.build/en/guides/integrations-guide/partytown/](https://docs.astro.build/en/guides/integrations-guide/partytown/)
70 | */
71 | withPartytown?: boolean;
72 | }
73 |
74 | async function getInjectableWebAnalyticsContent({
75 | mode,
76 | options,
77 | }: {
78 | mode: "development" | "production";
79 | options: OptionalExceptFor;
80 | }): Promise {
81 | const {
82 | autotrack = true,
83 | beforeSendHandler,
84 | domains = [],
85 | doNotTrack = false,
86 | endpointUrl = "https://cloud.umami.is",
87 | excludeHash = false,
88 | excludeSearch = false,
89 | hostUrl = "https://cloud.umami.is",
90 | id,
91 | tag,
92 | trackerScriptName = "script.js",
93 | withPartytown = false,
94 | } = options;
95 |
96 | const hostname = new URL(endpointUrl).hostname;
97 | const configAsString = [
98 | !autotrack ? `script.setAttribute("data-auto-track", "${autotrack}")` : "",
99 | beforeSendHandler ? `script.setAttribute("data-before-send", "${beforeSendHandler}")` : "",
100 | domains.length > 0
101 | ? `script.setAttribute("data-domains", "${domains.join(",")}")`
102 | : "",
103 | doNotTrack ? `script.setAttribute("data-do-not-track", "${doNotTrack}")` : "",
104 | excludeHash ? `script.setAttribute("data-exclude-hash", "${excludeHash}")` : "",
105 | excludeSearch ? `script.setAttribute("data-exclude-search", "${excludeSearch}")` : "",
106 | hostUrl !== "https://cloud.umami.is"
107 | ? `script.setAttribute("data-host-url", "${hostUrl}")`
108 | : "",
109 | tag ? `script.setAttribute("data-tag", "${tag}")` : "",
110 | withPartytown ? `script.setAttribute("type", "text/partytown")` : "",
111 | ]
112 | .filter(Boolean)
113 | .join(";\n");
114 |
115 | const commonScript = `
116 | var script = document.createElement("script");
117 | var viewTransitionsEnabled = document.querySelector("meta[name='astro-view-transitions-enabled']")?.content;
118 |
119 | script.setAttribute("src", "https://${hostname}/${trackerScriptName}");
120 | script.setAttribute("defer", true);
121 | script.setAttribute("data-website-id", "${id}");
122 | ${configAsString};
123 |
124 | if (!!viewTransitionsEnabled) {
125 | script.setAttribute("data-astro-rerun", true);
126 | }
127 |
128 | var head = document.querySelector("head");
129 | head.appendChild(script);
130 | `;
131 |
132 | if (mode === "development") {
133 | return `
134 | (function () {
135 | localStorage.setItem("umami.disabled", "1");
136 |
137 | ${commonScript}
138 | })()
139 | `;
140 | }
141 |
142 | return `
143 | (function () {
144 | ${commonScript}
145 | })()
146 | `;
147 | }
148 |
149 | export default function umamiIntegration(options: OptionalExceptFor): AstroIntegration {
150 | return {
151 | name: "@yeskunall/astro-umami",
152 | hooks: {
153 | "astro:config:setup": async ({ command, injectScript }) => {
154 | const script = await getInjectableWebAnalyticsContent({
155 | mode: command === "dev" ? "development" : "production",
156 | options,
157 | });
158 |
159 | injectScript("head-inline", script);
160 | },
161 | },
162 | };
163 | }
164 |
--------------------------------------------------------------------------------
/playground/tests/index.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from "@playwright/test";
2 |
3 | // NOTE: All tests query using the `website-id` data attribute because this is
4 | // the only required property as per the docs. In other words, if the script
5 | // exists on the page, then it should be safe to query based on this selector.
6 |
7 | test("`autotrack` disabled", async ({ page }) => {
8 | await page.goto("/");
9 |
10 | const src = await page.evaluate(
11 | async () => {
12 | const autotrack = document
13 | .querySelector(("script[data-website-id]"))
14 | ?.attributes
15 | .getNamedItem("data-auto-track")
16 | ?.textContent;
17 |
18 | return autotrack;
19 | },
20 | );
21 |
22 | expect(src).toBe("false");
23 | });
24 |
25 | test("`beforeSendHandler` is valid", async ({ page }) => {
26 | await page.goto("/");
27 |
28 | const src = await page.evaluate(
29 | async () => {
30 | const beforeSendHandler = document
31 | .querySelector(("script[data-website-id]"))
32 | ?.attributes
33 | .getNamedItem("data-before-send")
34 | ?.textContent;
35 |
36 | return beforeSendHandler;
37 | },
38 | );
39 |
40 | expect(src).toBe("beforeSendHandler");
41 | });
42 |
43 | test("`doNotTrack` enabled", async ({ page }) => {
44 | await page.goto("/");
45 |
46 | const src = await page.evaluate(
47 | async () => {
48 | const dnt = document
49 | .querySelector(("script[data-website-id]"))
50 | ?.attributes
51 | .getNamedItem("data-do-not-track")
52 | ?.textContent;
53 |
54 | return dnt;
55 | },
56 | );
57 |
58 | expect(src).toBe("true");
59 | });
60 |
61 | test("`excludeHash` enabled", async ({ page }) => {
62 | await page.goto("/");
63 |
64 | const src = await page.evaluate(
65 | async () => {
66 | const excludeHash = document
67 | .querySelector(("script[data-website-id]"))
68 | ?.attributes
69 | .getNamedItem("data-exclude-hash")
70 | ?.textContent;
71 |
72 | return excludeHash;
73 | },
74 | );
75 |
76 | expect(src).toBe("true");
77 | });
78 |
79 | test("`excludeSearch` enabled", async ({ page }) => {
80 | await page.goto("/");
81 |
82 | const src = await page.evaluate(
83 | async () => {
84 | const excludeSearch = document
85 | .querySelector(("script[data-website-id]"))
86 | ?.attributes
87 | .getNamedItem("data-exclude-search")
88 | ?.textContent;
89 |
90 | return excludeSearch;
91 | },
92 | );
93 |
94 | expect(src).toBe("true");
95 | });
96 |
97 | test("`src` matches", async ({ page }) => {
98 | await page.goto("/");
99 |
100 | const src = await page.evaluate(
101 | async () => {
102 | const src = document
103 | .querySelector(("script[data-website-id]"))
104 | ?.attributes
105 | .getNamedItem("src")
106 | ?.textContent;
107 |
108 | return src;
109 | },
110 | );
111 |
112 | expect(src).toBe("https://cloud.umami.is/script.js");
113 | });
114 |
115 | test("`id` matches", async ({ page }) => {
116 | await page.goto("/");
117 |
118 | const id = await page.evaluate(
119 | async () => {
120 | const id = document
121 | .querySelector(("script[data-website-id]"))
122 | ?.attributes
123 | .getNamedItem("data-website-id")
124 | ?.textContent;
125 |
126 | return id;
127 | },
128 | );
129 |
130 | expect(id).toBe("94db1cb1-74f4-4a40-ad6c-962362670409");
131 | });
132 |
133 | test("`domains` match", async ({ page }) => {
134 | await page.goto("/");
135 |
136 | const domains = await page.evaluate(
137 | async () => {
138 | const domains = document
139 | .querySelector(("script[data-website-id]"))
140 | ?.attributes
141 | .getNamedItem("data-domains")
142 | ?.textContent;
143 |
144 | return domains;
145 | },
146 | );
147 |
148 | expect(domains).toBe("example.com,com.example");
149 | });
150 |
151 | test("`hostUrl` matches", async ({ page }) => {
152 | await page.goto("/");
153 |
154 | const hostUrl = await page.evaluate(
155 | async () => {
156 | const hostUrl = document
157 | .querySelector(("script[data-website-id]"))
158 | ?.attributes
159 | .getNamedItem("data-host-url")
160 | ?.textContent;
161 |
162 | return hostUrl;
163 | },
164 | );
165 |
166 | expect(hostUrl).toBe("https://analytics.eu.umami.is");
167 | });
168 |
169 | test("`tag` matches", async ({ page }) => {
170 | await page.goto("/");
171 |
172 | const src = await page.evaluate(
173 | async () => {
174 | const beforeSendHandler = document
175 | .querySelector(("script[data-website-id]"))
176 | ?.attributes
177 | .getNamedItem("data-tag")
178 | ?.textContent;
179 |
180 | return beforeSendHandler;
181 | },
182 | );
183 |
184 | expect(src).toBe("test-tag");
185 | });
186 |
187 | test("`withPartytown` enabled", async ({ page }) => {
188 | await page.goto("/");
189 |
190 | const hostUrl = await page.evaluate(
191 | async () => {
192 | const hostUrl = document
193 | .querySelector(("script[data-website-id]"))
194 | ?.attributes
195 | .getNamedItem("type")
196 | ?.textContent;
197 |
198 | return hostUrl;
199 | },
200 | );
201 |
202 | expect(hostUrl).toBe("text/partytown");
203 | });
204 |
--------------------------------------------------------------------------------