Skip to content

Commit

Permalink
Add e2e tests with playwright (#2)
Browse files Browse the repository at this point in the history
* Minor fixes

* Implement e2e tests with using playwright

* Use pnpm in github actions file

* Use npm again

* Fix playwright baseURL

* Bypass vercel auth for CI e2e tests

* Fix e2e actions file

* Fix playwright config file

* Fix wrong base url in actions file

* Remove package-lock

* Set retries to 3

* Add docker image to github action

* Remove firefox tests as it seems to have some issues
  • Loading branch information
abel-castro authored Aug 6, 2024
1 parent 895e16f commit 3a1de8c
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 25 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Playwright Tests
on:
deployment_status:
jobs:
e2e-tests:
if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success'
timeout-minutes: 60
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.46.0-jammy
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Install dependencies
run: npm install -g pnpm && pnpm install
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
env:
PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.environment_url }}
run: pnpm exec playwright test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ https://tailwindcss.com/docs/background-color

https://nextjs.org/docs/app/building-your-application/optimizing/metadata

### Run playwright on Gitlab

https://playwright.dev/docs/ci-intro#on-deployment
https://cushionapp.com/journal/how-to-use-playwright-with-github-actions-for-e2e-testing-of-vercel-preview

## Getting Started

Provide somehow an Rest-API that returns blog posts as defined in `definitions.ts`.

Create a `.env` file based on the `.env.template`

Install dependencies:

```bash
pnpm i
```
Expand All @@ -49,3 +55,19 @@ pnpm dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

## Tests

This project provides e2e test with playwright.

Headless run:

```sh
pnpm exec playwright test
```

Run with UI:

```sh
pnpm exec playwright test --ui
```
13 changes: 9 additions & 4 deletions app/components/posts/post-pagination.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { generatePagination } from "@/app/lib/pagination";
import { generatePagination } from "../../lib/pagination";
import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import Link from "next/link";
Expand All @@ -20,7 +20,7 @@ export default function Pagination({ totalPages }: { totalPages: number }) {

return (
<>
<div className="inline-flex">
<div data-testid="pagination" className="inline-flex">
<PaginationArrow
direction="left"
href={createPageURL(currentPage - 1)}
Expand Down Expand Up @@ -115,10 +115,15 @@ function PaginationArrow({
<ArrowRightIcon className="w-4" />
);

const testIdValue =
direction === "left" ? "pagination-back" : "pagination-forward";

return isDisabled ? (
<div className={className}>{icon}</div>
<div className={className} data-testid={testIdValue}>
{icon}
</div>
) : (
<Link className={className} href={href}>
<Link className={className} href={href} data-testid={testIdValue}>
{icon}
</Link>
);
Expand Down
7 changes: 5 additions & 2 deletions app/components/posts/post-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ export default function Search({ placeholder }: { placeholder: string }) {
}, 300);

return (
<div className="relative flex mt-4 sm:mt-0 w-full sm:w-auto sm:flex justify-center">
<form
data-testid="search-form"
className="relative flex mt-4 sm:mt-0 w-full sm:w-auto sm:flex justify-center"
>
<label htmlFor="search" className="sr-only">
Search
</label>
Expand All @@ -35,6 +38,6 @@ export default function Search({ placeholder }: { placeholder: string }) {
defaultValue={searchParams.get("query")?.toString()}
/>
<MagnifyingGlassIcon className="hidden lg:block absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900 " />
</div>
</form>
);
}
2 changes: 1 addition & 1 deletion app/components/posts/post-single.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default async function PostSingle({
hasLink?: boolean;
}) {
return (
<div key={slug} className="mb-8">
<div key={slug} className="post-element mb-8">
<PostTitle title={title} slug={slug} hasLink={hasLink} />
<PostDate date={date} />
<PostTags tags={tags} />
Expand Down
4 changes: 2 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: {
template: "%s | AbelCastro.Dev",
default: "AbelCastro.Dev",
template: "%s | abelcastro.dev",
default: "abelcastro.dev",
},
description:
"My personal blog where I (and LLMs 🤖) write about coding-related topics.",
Expand Down
2 changes: 1 addition & 1 deletion app/lib/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type Post = {

export type PostsAPIResponse = {
count: number;
next: string;
next: string | null;
previous: string | null;
results: Post[];
};
2 changes: 1 addition & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default async function Home({
<>
<BlogHeader />
<main className="flex-grow p-4">
<div className="max-w-4xl mx-auto">
<div id="post-container" className="max-w-4xl mx-auto">
<Suspense key={query + currentPage} fallback={<PostListSkeleton />}>
<PostList posts={posts} />
</Suspense>
Expand Down
52 changes: 52 additions & 0 deletions e2e-tests/home.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { test, expect } from "@playwright/test";

test("main elements are visible", async ({ page }) => {
await page.goto("/");

await expect(page).toHaveTitle(/abelcastro.dev/);
await expect(
page.getByRole("heading", { name: "abelcastro.dev" })
).toBeVisible();

// The page should have 3 posts
const posts = page.locator("#post-container > .post-element");
await expect(posts).toHaveCount(3);

// Pagination
await expect(page.getByTestId("pagination")).toBeVisible();

// Back button pagination
const paginationBack = page.getByTestId("pagination-back");
await expect(paginationBack).toBeVisible();
// We are loading the first page, so the back button should be disabled/not clickable
await expect(
await paginationBack.evaluate((e) =>
(e as HTMLInputElement).hasAttribute("href")
)
).toBeFalsy();

// Forward button pagination
const paginationForward = page.getByTestId("pagination-forward");
await expect(paginationForward).toBeVisible();

// The forward button should be clickable as we are on the first page
await expect(
await paginationForward.evaluate((e) =>
(e as HTMLInputElement).hasAttribute("href")
)
).toBeTruthy();

// Search form
await expect(page.getByTestId("search-form")).toBeVisible();
});

test("search works", async ({ page }) => {
await page.goto("/");

await expect(page.getByTestId("search-form")).toBeVisible();
await page.getByTestId("search-form").locator("input").fill("My first post");

// The page should have 1 posts
const posts = page.locator("#post-container > .post-element");
await expect(posts).toHaveCount(1);
});
1 change: 0 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const mdxOptions = {

const nextConfig = {
pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
transpilePackages: ["next-mdx-remote"],
};

export default withMDX(mdxOptions)(nextConfig);
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"use-debounce": "^10.0.2"
},
"devDependencies": {
"@playwright/test": "^1.45.3",
"@types/mapbox__rehype-prism": "^0.8.3",
"@types/node": "^20.14.13",
"@types/react": "^18.3.3",
Expand Down
59 changes: 59 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { defineConfig, devices } from "@playwright/test";

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./e2e-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 ? 3 : 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: {
baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL ?? "http://localhost:3000",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
// https://vercel.com/docs/security/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation
extraHTTPHeaders: {
"x-vercel-protection-bypass": process.env
.VERCEL_AUTOMATION_BYPASS_SECRET as string,
},
},

/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},

{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},

/* Test against mobile viewports. */
{
name: "Mobile Chrome",
use: { ...devices["Pixel 5"] },
},
{
name: "Mobile Safari",
use: { ...devices["iPhone 12"] },
},
],
});
Loading

0 comments on commit 3a1de8c

Please sign in to comment.