summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Anshul Gupta <ansg191@anshulg.com> 2025-06-06 14:18:07 -0700
committerGravatar Anshul Gupta <ansg191@anshulg.com> 2025-06-06 14:18:07 -0700
commit108f26a3874e5f8649bf4d128aa07c9b35aa6a10 (patch)
treed7f1e145ecca9d6e5e450bdf38353edd93aefe3f
parent774b221be12316230019b90fb597b4590ec37a01 (diff)
downloadanshulg-com-108f26a3874e5f8649bf4d128aa07c9b35aa6a10.tar.gz
anshulg-com-108f26a3874e5f8649bf4d128aa07c9b35aa6a10.tar.zst
anshulg-com-108f26a3874e5f8649bf4d128aa07c9b35aa6a10.zip
Add playwright tests
Diffstat (limited to '')
-rw-r--r--.github/workflows/playwright.yml25
-rw-r--r--.gitignore6
-rw-r--r--bun.lock18
-rw-r--r--package.json4
-rw-r--r--playwright.config.ts79
-rw-r--r--src/components/server/Uptime.astro43
-rw-r--r--tests/index.spec.ts48
7 files changed, 203 insertions, 20 deletions
diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
new file mode 100644
index 0000000..f74fdd3
--- /dev/null
+++ b/.github/workflows/playwright.yml
@@ -0,0 +1,25 @@
+name: Playwright Tests
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+jobs:
+ test:
+ timeout-minutes: 60
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4.2.2
+ - uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+ - name: Install Playwright Browsers
+ run: bunx playwright install --with-deps
+ - name: Run Playwright tests
+ run: bunx playwright test
+ - uses: actions/upload-artifact@v4
+ if: ${{ !cancelled() }}
+ with:
+ name: playwright-report
+ path: playwright-report/
+ retention-days: 30
diff --git a/.gitignore b/.gitignore
index 0bacd0d..9c1b64a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,9 @@ pnpm-debug.log*
# ESLint cache
.eslintcache
+
+# Playwright
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/bun.lock b/bun.lock
index 0a49593..e43b570 100644
--- a/bun.lock
+++ b/bun.lock
@@ -12,6 +12,8 @@
"tailwindcss": "^4.1.8",
},
"devDependencies": {
+ "@playwright/test": "^1.52.0",
+ "@types/node": "^22.15.30",
"@typescript-eslint/parser": "8.33.1",
"eslint": "9.28.0",
"eslint-plugin-astro": "1.3.1",
@@ -188,6 +190,8 @@
"@pkgr/core": ["@pkgr/core@0.2.5", "", {}, "sha512-YRx7tFgLkrpFkDAzVSV5sUJydmf2ZDrW+O3IbQ1JyeMW7B0FiWroFJTnR4/fD9CsusnAn4qRUcbb5jFnZSd6uw=="],
+ "@playwright/test": ["@playwright/test@1.52.0", "", { "dependencies": { "playwright": "1.52.0" }, "bin": { "playwright": "cli.js" } }, "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g=="],
+
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.41.1", "", { "os": "android", "cpu": "arm" }, "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw=="],
@@ -292,7 +296,7 @@
"@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],
- "@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="],
+ "@types/node": ["@types/node@22.15.30", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA=="],
"@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="],
@@ -592,7 +596,7 @@
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
- "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+ "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -990,6 +994,10 @@
"pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="],
+ "playwright": ["playwright@1.52.0", "", { "dependencies": { "playwright-core": "1.52.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw=="],
+
+ "playwright-core": ["playwright-core@1.52.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg=="],
+
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
"postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="],
@@ -1350,11 +1358,15 @@
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
+ "rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "sitemap/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="],
+
"slice-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
- "vite/@types/node": ["@types/node@22.15.29", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ=="],
+ "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
diff --git a/package.json b/package.json
index 7f06a06..fe36892 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,8 @@
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint .",
+ "test": "playwright test",
+ "test:ui": "playwright test --ui",
"prepare": "husky || true"
},
"dependencies": {
@@ -22,6 +24,8 @@
"tailwindcss": "^4.1.8"
},
"devDependencies": {
+ "@playwright/test": "^1.52.0",
+ "@types/node": "^22.15.30",
"@typescript-eslint/parser": "8.33.1",
"eslint": "9.28.0",
"eslint-plugin-astro": "1.3.1",
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 0000000..b9d5922
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,79 @@
+import { defineConfig, devices } from "@playwright/test";
+
+/**
+ * Read environment variables from file.
+ * https://github.com/motdotla/dotenv
+ */
+// import dotenv from 'dotenv';
+// import path from 'path';
+// dotenv.config({ path: path.resolve(__dirname, '.env') });
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: "./tests",
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* Retry on CI only */
+ retries: process.env.CI ? 2 : 0,
+ /* Opt out of parallel tests on CI. */
+ workers: process.env.CI ? 1 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: "html",
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: "http://localhost:4321",
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: "on-first-retry",
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: "chromium",
+ use: { ...devices["Desktop Chrome"] },
+ },
+
+ {
+ name: "firefox",
+ use: { ...devices["Desktop Firefox"] },
+ },
+
+ {
+ name: "webkit",
+ use: { ...devices["Desktop Safari"] },
+ },
+
+ /* Test against mobile viewports. */
+ {
+ name: "Mobile Chrome",
+ use: { ...devices["Pixel 5"] },
+ },
+ {
+ name: "Mobile Safari",
+ use: { ...devices["iPhone 12"] },
+ },
+
+ /* Test against branded browsers. */
+ // {
+ // name: 'Microsoft Edge',
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
+ // },
+ // {
+ // name: 'Google Chrome',
+ // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
+ // },
+ ],
+
+ /* Run your local dev server before starting the tests */
+ webServer: {
+ command: "bun run build && bun run preview",
+ url: "http://localhost:4321",
+ reuseExistingServer: !process.env.CI,
+ },
+});
diff --git a/src/components/server/Uptime.astro b/src/components/server/Uptime.astro
index ff65f3e..c034ec3 100644
--- a/src/components/server/Uptime.astro
+++ b/src/components/server/Uptime.astro
@@ -10,11 +10,14 @@ interface Props {
}
const { name } = Astro.props;
+let show = true;
+
// Ensure ENV variables are set
if (!KUMA_URL || !KUMA_API_KEY) {
- Astro.response.status = 500;
- Astro.response.statusText = "Internal Server Error";
- return;
+ console.warn(
+ "Uptime Kuma URL or API Key is not set. Skipping Uptime component.",
+ );
+ show = false;
}
// Fetch the metrics from Uptime Kuma
@@ -25,9 +28,10 @@ const response = await fetch(`${KUMA_URL}/metrics`, {
});
if (!response.ok) {
- Astro.response.status = response.status;
- Astro.response.statusText = response.statusText;
- return;
+ console.warn(
+ `Failed to fetch metrics from Uptime Kuma: ${response.status} ${response.statusText}`,
+ );
+ show = false;
}
// Parse the metrics to find the status of the monitor with the given name
@@ -50,9 +54,8 @@ const status = metrics
.map((m) => m.status)[0];
if (status == null) {
- Astro.response.status = 404;
- Astro.response.statusText = "Not Found";
- return;
+ console.warn(`Monitor with name "${name}" not found in Uptime Kuma metrics.`);
+ show = false;
}
// Map the status to a color
@@ -72,11 +75,17 @@ Astro.response.headers.set(
);
---
-<span class="absolute -top-1.5 -right-1 w-2.5 h-2.5">
- <span
- class="absolute inset-0 rounded-full bg-gray-500 opacity-75 motion-safe:animate-ping"
- style={style}></span>
- <span
- class="relative block w-2.5 h-2.5 rounded-full bg-gray-500 shadow shadow-green-400/50"
- style={style}></span>
-</span>
+{
+ show && (
+ <span class="absolute -top-1.5 -right-1 w-2.5 h-2.5">
+ <span
+ class="absolute inset-0 rounded-full bg-gray-500 opacity-75 motion-safe:animate-ping"
+ style={style}
+ />
+ <span
+ class="relative block w-2.5 h-2.5 rounded-full bg-gray-500 shadow shadow-green-400/50"
+ style={style}
+ />
+ </span>
+ )
+}
diff --git a/tests/index.spec.ts b/tests/index.spec.ts
new file mode 100644
index 0000000..b804a2a
--- /dev/null
+++ b/tests/index.spec.ts
@@ -0,0 +1,48 @@
+import {
+ test,
+ expect,
+ type Locator,
+ type BrowserContext,
+} from "@playwright/test";
+
+test.beforeEach(async ({ page }) => {
+ await page.goto("/");
+});
+
+test("has title", async ({ page }) => {
+ await expect(page).toHaveTitle("Anshul Gupta");
+});
+
+test("has header", async ({ page }) => {
+ let header = page.locator("header");
+ await expect(header).toBeVisible();
+ await expect(header.locator("div").first()).toHaveText("ANSHUL GUPTA (7)");
+ await expect(header.locator("div").last()).toHaveText("ANSHUL GUPTA (7)");
+ await expect(header.locator("h1")).toHaveText(
+ "Miscellaneous Information Manual",
+ );
+});
+
+test("has Email link", async ({ page }) => {
+ let emailLink = page.getByRole("link", { name: "ansg191@anshulg.com" });
+ await expect(emailLink).toBeVisible();
+ await expect(emailLink).toHaveText("ansg191@anshulg.com");
+ await expect(emailLink).toHaveAttribute("href", "mailto:ansg191@anshulg.com");
+});
+
+test("has GitHub link", async ({ page, context }) => {
+ let ghLink = page.getByRole("link", { name: "ansg191" }).nth(1);
+ await checkLinkNewTab(context, ghLink, "https://github.com/ansg191");
+});
+
+async function checkLinkNewTab(
+ ctx: BrowserContext,
+ link: Locator,
+ expUrl: string,
+) {
+ const pagePromise = ctx.waitForEvent("page");
+ await expect(link).toBeVisible();
+ await link.click();
+ const newPage = await pagePromise;
+ await expect(newPage).toHaveURL(expUrl);
+}