}>
diff --git a/e2e-tests/home.spec.ts b/e2e-tests/home.spec.ts
new file mode 100644
index 0000000..b3eca11
--- /dev/null
+++ b/e2e-tests/home.spec.ts
@@ -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);
+});
diff --git a/next.config.mjs b/next.config.mjs
index 0833241..b26ad24 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -13,7 +13,6 @@ const mdxOptions = {
const nextConfig = {
pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
- transpilePackages: ["next-mdx-remote"],
};
export default withMDX(mdxOptions)(nextConfig);
diff --git a/package.json b/package.json
index 0732dfe..cd4ab49 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 0000000..d083e92
--- /dev/null
+++ b/playwright.config.ts
@@ -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"] },
+ },
+ ],
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d733c42..b766362 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -31,10 +31,10 @@ importers:
version: 2.0.13
'@vercel/analytics':
specifier: ^1.3.1
- version: 1.3.1(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
+ version: 1.3.1(next@14.2.3(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@vercel/speed-insights':
specifier: ^1.0.12
- version: 1.0.12(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
+ version: 1.0.12(next@14.2.3(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
clsx:
specifier: ^2.1.1
version: 2.1.1
@@ -49,7 +49,7 @@ importers:
version: 0.13.0
next:
specifier: 14.2.3
- version: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 14.2.3(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-mdx-remote:
specifier: ^5.0.0
version: 5.0.0(@types/react@18.3.3)(react@18.3.1)
@@ -93,6 +93,9 @@ importers:
specifier: ^10.0.2
version: 10.0.2(react@18.3.1)
devDependencies:
+ '@playwright/test':
+ specifier: ^1.45.3
+ version: 1.45.3
'@types/mapbox__rehype-prism':
specifier: ^0.8.3
version: 0.8.3
@@ -308,6 +311,11 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
+ '@playwright/test@1.45.3':
+ resolution: {integrity: sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
'@rushstack/eslint-patch@1.10.4':
resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==}
@@ -1171,6 +1179,11 @@ packages:
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+ fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -1982,6 +1995,16 @@ packages:
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
engines: {node: '>= 6'}
+ playwright-core@1.45.3:
+ resolution: {integrity: sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ playwright@1.45.3:
+ resolution: {integrity: sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==}
+ engines: {node: '>=18'}
+ hasBin: true
+
possible-typed-array-names@1.0.0:
resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
engines: {node: '>= 0.4'}
@@ -2865,6 +2888,10 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
+ '@playwright/test@1.45.3':
+ dependencies:
+ playwright: 1.45.3
+
'@rushstack/eslint-patch@1.10.4': {}
'@swc/counter@0.1.3': {}
@@ -3004,16 +3031,16 @@ snapshots:
'@ungap/structured-clone@1.2.0': {}
- '@vercel/analytics@1.3.1(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
+ '@vercel/analytics@1.3.1(next@14.2.3(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
dependencies:
server-only: 0.0.1
optionalDependencies:
- next: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ next: 14.2.3(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
- '@vercel/speed-insights@1.0.12(next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
+ '@vercel/speed-insights@1.0.12(next@14.2.3(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
optionalDependencies:
- next: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ next: 14.2.3(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
'@webassemblyjs/ast@1.12.1':
@@ -3639,7 +3666,7 @@ snapshots:
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0)
eslint-plugin-react: 7.35.0(eslint@8.57.0)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
@@ -3663,7 +3690,7 @@ snapshots:
enhanced-resolve: 5.17.1
eslint: 8.57.0
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
- eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.6
is-core-module: 2.15.0
@@ -3685,7 +3712,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
+ eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
dependencies:
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
@@ -3923,6 +3950,9 @@ snapshots:
fs.realpath@1.0.0: {}
+ fsevents@2.3.2:
+ optional: true
+
fsevents@2.3.3:
optional: true
@@ -4886,7 +4916,7 @@ snapshots:
- '@types/react'
- supports-color
- next@14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ next@14.2.3(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 14.2.3
'@swc/helpers': 0.5.5
@@ -4907,6 +4937,7 @@ snapshots:
'@next/swc-win32-arm64-msvc': 14.2.3
'@next/swc-win32-ia32-msvc': 14.2.3
'@next/swc-win32-x64-msvc': 14.2.3
+ '@playwright/test': 1.45.3
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
@@ -5050,6 +5081,14 @@ snapshots:
pirates@4.0.6: {}
+ playwright-core@1.45.3: {}
+
+ playwright@1.45.3:
+ dependencies:
+ playwright-core: 1.45.3
+ optionalDependencies:
+ fsevents: 2.3.2
+
possible-typed-array-names@1.0.0: {}
postcss-import@15.1.0(postcss@8.4.40):
diff --git a/tsconfig.json b/tsconfig.json
index c87d50b..c3d47e0 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,9 +22,13 @@
"name": "next"
}
],
+ "baseUrl": "./",
"paths": {
"@/*": [
"./*"
+ ],
+ "@/components/*": [
+ "components/*"
]
}
},
@@ -32,8 +36,7 @@
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
- ".next/types/**/*.ts",
- "app/components/posts/post-single.tsx"
+ ".next/types/**/*.ts"
],
"exclude": [
"node_modules"