Skip to content

Commit

Permalink
test: add e2e tests with playwright
Browse files Browse the repository at this point in the history
  • Loading branch information
Ernxst committed Jun 29, 2024
1 parent f36ff09 commit 9e1e4f2
Show file tree
Hide file tree
Showing 21 changed files with 549 additions and 9 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,36 @@ jobs:

- name: 🧪 Run Type Tests
run: bun run test:types --continue --filter='!./examples/*'

playwright:
name: "🧪 Playwright"
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 2

- name: 📤 Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.13.1

- name: 📤 Install Bun
uses: oven-sh/setup-bun@v1

- name: 📤 Install dependencies
run: bun install

- name: 📤 Install Playwright browsers
run: bunx playwright install --with-deps
working-directory: packages/core

- name: 🧪 Run Playwright tests
run: bun test:e2e --continue -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ coverage

.nyc_output

# playwright
**/tests/e2e/report
**/tests/e2e/blob-report
**/tests/e2e/results

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

.grunt
Expand Down
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"format": "biome format . --write",
"---------------------------------------------------------------------------------": "",
"test": "turbo run test",
"test:e2e": "turbo run test:e2e",
"test:types": "turbo run test:types",
"test:watch": "turbo run test:watch",
"----------------------------------------------------------------------------------": "",
Expand Down
5 changes: 4 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"test": "vitest run",
"test:types": "vitest run --typecheck",
"test:watch": "vitest",
"test:e2e": "playwright test",
"---------------------------------------------------------------------------------": "",
"postinstall": "svelte-kit sync"
},
Expand Down Expand Up @@ -127,11 +128,13 @@
"zod": "^3.22.2"
},
"devDependencies": {
"@sveltejs/adapter-static": "3.0.2",
"@playwright/test": "1.45.0",
"@sveltejs/adapter-auto": "3.2.2",
"@sveltejs/vite-plugin-svelte": "3.1.1",
"@testing-library/jest-dom": "6.4.6",
"@testing-library/svelte": "5.1.0",
"@testing-library/user-event": "14.5.2",
"@types/node": "20.14.9",
"jsdom": "24.1.0",
"svelte-check": "3.8.1",
"tslib": "2.6.3",
Expand Down
71 changes: 71 additions & 0 deletions packages/core/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { resolve } from "node:path";
import { defineConfig, devices } from "@playwright/test";

function playwrightDir(partialPath: string) {
return resolve("./tests/e2e", partialPath);
}

const CI = !!process.env.CI;

export default defineConfig({
testDir: playwrightDir("specs"),
outputDir: playwrightDir("results"),
webServer: {
command: "bun vite build && bun vite preview",
port: 4173,
},
use: {
screenshot: "only-on-failure",
trace: "retain-on-failure",
},
testMatch: /(.+\.)?(test|spec)\.[jt]s/,
reporter: CI
? [["github"]]
: [
[
"html",
{ outputFolder: playwrightDir("./report"), open: "on-failure" },
],
],
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
},
},
{
name: "firefox",
use: {
...devices["Desktop Firefox"],
},
},
{
name: "webkit",
use: {
...devices["Desktop Safari"],
},
},
{
name: "Mobile Chrome",
use: { ...devices["Pixel 5"] },
},
{
name: "Mobile Safari",
use: { ...devices["iPhone 14"] },
},
/** Test branded browsers */
{
name: "google",
use: {
...devices["Desktop Google Chrome"],
},
},
{
name: "msedge",
use: {
...devices["Desktop Edge"],
},
},
],
});
38 changes: 38 additions & 0 deletions packages/core/src/routes/(test)/multiselect/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
import { page } from "$app/stores";
import { useMultiSelectFilters } from "./_hooks/multi-select";
const [q, helpers] = useMultiSelectFilters($page.url);
function updateCategories(category: string) {
const categories = q.categories.includes(category)
? q.categories.filter((c) => c !== category)
: [...q.categories, category];
helpers.update({ categories });
}
</script>

<ul>
<li>
<label>
<input
type="checkbox"
value="books"
onchange={() => updateCategories("books")}
checked={q.categories.includes("books")}
/>
Books
</label>
</li>
<li>
<label>
<input
type="checkbox"
value="electronics"
onchange={() => updateCategories("electronics")}
checked={q.categories.includes("electronics")}
/>
Electronics
</label>
</li>
</ul>
15 changes: 15 additions & 0 deletions packages/core/src/routes/(test)/multiselect/_hooks/multi-select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { sveltekit } from "$lib/adapters/sveltekit";
import { createUseQueryParams } from "$lib/create-params.svelte";
import { z } from "zod";

export type MultiSelect = z.infer<typeof MultiSelect>;
const MultiSelect = z.object({
categories: z
.union([z.string().array(), z.string()])
.default([])
.transform((c) => (Array.isArray(c) ? c : [c])),
});

export const useMultiSelectFilters = createUseQueryParams(MultiSelect, {
adapter: sveltekit(),
});
20 changes: 20 additions & 0 deletions packages/core/src/routes/(test)/pagination/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script>
import { page } from "$app/stores";
import { usePagination } from "./_hooks/pagination";
const [q] = usePagination($page.url);
const nextPage = () => {
q.page = Math.min(q.page + 1, 10);
};
const prevPage = () => {
q.page = Math.max(q.page - 1, 1);
};
</script>

<h1>Current Page: {q.page}</h1>
<h2>Page Size: {q.pageSize}</h2>

<button onclick={prevPage} disabled={q.page === 1}>Previous</button>
<button onclick={nextPage} disabled={q.page === 10}>Next</button>
13 changes: 13 additions & 0 deletions packages/core/src/routes/(test)/pagination/_hooks/pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { sveltekit } from "$lib/adapters/sveltekit";
import { createUseQueryParams } from "$lib/create-params.svelte";
import { z } from "zod";

export type Pagination = z.infer<typeof Pagination>;
const Pagination = z.object({
page: z.coerce.number().default(1),
pageSize: z.coerce.number().default(10),
});

export const usePagination = createUseQueryParams(Pagination, {
adapter: sveltekit(),
});
13 changes: 13 additions & 0 deletions packages/core/src/routes/(test)/search/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script lang="ts">
import { page } from "$app/stores";
import { useSearchFilters } from "./_hooks/search";
const [q] = useSearchFilters($page.url);
</script>

<input type="text" bind:value={q.q} placeholder="Search..." role="searchbox" />
<select bind:value={q.category}>
<option value="">All Categories</option>
<option value="books">Books</option>
<option value="electronics">Electronics</option>
</select>
14 changes: 14 additions & 0 deletions packages/core/src/routes/(test)/search/_hooks/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { sveltekit } from "$lib/adapters/sveltekit";
import { createUseQueryParams } from "$lib/create-params.svelte";
import * as v from "valibot";

export type Search = v.InferOutput<typeof Search>;
const Search = v.object({
q: v.optional(v.string(), ""),
category: v.optional(v.string()),
});

export const useSearchFilters = createUseQueryParams(Search, {
debounce: 1000,
adapter: sveltekit(),
});
6 changes: 6 additions & 0 deletions packages/core/src/routes/(test)/ssr/+layout.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useQueryParams } from "./params";

export function load({ url }) {
const [params] = useQueryParams(url);
params.count = 10;
}
9 changes: 9 additions & 0 deletions packages/core/src/routes/(test)/ssr/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script lang="ts">
import { page } from "$app/stores";
import { useQueryParams } from "./params";
const [q] = useQueryParams($page.url);
</script>

<h1>Count: {$page.url.searchParams.get("count")}</h1>
<h2>Count: {q.count}</h2>
10 changes: 10 additions & 0 deletions packages/core/src/routes/(test)/ssr/params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { sveltekit } from "$lib/adapters/sveltekit";
import { createUseQueryParams } from "$lib/create-params.svelte";
import { z } from "zod";

export const useQueryParams = createUseQueryParams(
{
count: z.coerce.number().optional().default(0),
},
{ adapter: sveltekit() }
);
96 changes: 96 additions & 0 deletions packages/core/src/routes/(test)/tabs/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<script lang="ts">
import { page } from "$app/stores";
import { useTabs } from "./_hooks/tabs";
const [q] = useTabs($page.url);
const onHomeTab = $derived(q.tab === "home");
const onUserTab = $derived(q.tab === "users");
</script>

<header class="tabs">
<div role="tablist" aria-label="Tabs" aria-orientation="horizontal">
<button
id="home-tab"
role="tab"
disabled={onHomeTab}
aria-selected={onHomeTab}
aria-controls="home"
tabindex={onHomeTab ? 0 : -1}
onclick={() => (q.tab = "home")}
>
Home
</button>
<button
id="users-tab"
role="tab"
disabled={onUserTab}
aria-selected={onUserTab}
aria-controls="users"
tabindex={onUserTab ? 0 : -1}
onclick={() => (q.tab = "users")}
>
Users
</button>
</div>
</header>

<section>
<div
id="home"
role="tabpanel"
aria-labelledby="home-tab"
tabindex="0"
hidden={!onHomeTab}
>
<h1 id="title">Home Page</h1>
<p>You are on the home page</p>
</div>
<div
id="users"
role="tabpanel"
aria-labelledby="users-tab"
tabindex="0"
hidden={!onUserTab}
>
<h1 id="title">Users Page</h1>
<p>You are on the users page</p>
</div>
</section>

<style>
.tabs {
padding: 1em;
}
[role="tablist"] {
margin-bottom: -1px;
}
[role="tab"] {
position: relative;
z-index: 1;
background: white;
border-radius: 5px 5px 0 0;
border: 1px solid grey;
border-bottom: 0;
padding: 0.2em;
}
[role="tab"][aria-selected="true"] {
z-index: 3;
}
[role="tabpanel"] {
position: relative;
padding: 0 0.5em 0.5em 0.7em;
border: 1px solid grey;
border-radius: 0 0 5px 5px;
background: white;
z-index: 2;
}
[role="tabpanel"]:focus {
border-color: orange;
outline: 1px solid orange;
}
</style>
Loading

0 comments on commit 9e1e4f2

Please sign in to comment.