Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ on:
branches:
- main
pull_request:
types: [opened, synchronize]
branches:
- main

jobs:
check-and-test:
Expand Down Expand Up @@ -40,6 +41,40 @@ jobs:
- name: Run tests
run: npm run test

web-e2e-tests:
name: Web E2E Tests
timeout-minutes: 15
runs-on: ubuntu-latest
needs: [check-and-test]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
steps:
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 2

# automated-testing:
# name: E2E Testing
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'

- name: Install dependencies for web
run: npm install --filter=@tech-companies-portugal/web

- name: Install Playwright browsers
run: npx playwright install chromium --with-deps

- name: Run Playwright tests
run: npm run test:e2e:web

- name: Upload test results
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: playwright-report-web
path: ./apps/web/playwright-report/
retention-days: 2

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ The main goal is to provide a better way to explore tech companies in Portugal.
## Monorepo structure 📦

- `apps/web`: The main web app
- `packages/analytics`: Analytics package
- `packages/analytics`: Analytics utils
- `tooling/typescript`: TypeScript configuration
- `tooling/tailwind`: Tailwind configuration

Expand All @@ -23,6 +23,7 @@ The main goal is to provide a better way to explore tech companies in Portugal.
- [TypeScript](https://www.typescriptlang.org/)
- [Shadcn UI](https://ui.shadcn.com) - UI components
- [Vercel](https://vercel.com/) - Hosting and CI/CD
- [Playwright](https://playwright.dev/) - E2E testing
- [Biome](https://biomejs.dev/) - Formatting and linting
- [Motion](https://motion.dev/) - Animation library
- [Nuqs](https://nuqs.47ng.com) - URL query state management (client and server support + some other cool features out of the box)
Expand Down
7 changes: 7 additions & 0 deletions apps/web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
16 changes: 10 additions & 6 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@
"lint": "biome check . --write",
"format": "biome format --write .",
"check-types": "tsc --noEmit",
"clean": "git clean -xdf .next .turbo node_modules",
"test": "echo 'No tests to run'"
"clean": "git clean -xdf .next .turbo node_modules playwright-report test-results",
"test": "echo 'No tests to run'",
"test:e2e": "start-server-and-test start http://localhost:3000 'playwright test'",
"test:e2e:ui": "start-server-and-test start http://localhost:3000 'playwright test --ui'"
},
"dependencies": {
"@radix-ui/react-collapsible": "1.1.2",
"@radix-ui/react-label": "2.1.1",
"@radix-ui/react-select": "2.1.4",
"@radix-ui/react-slot": "1.1.1",
"@tech-companies-portugal/analytics": "*",
"cheerio": "1.0.0-rc.12",
"class-variance-authority": "0.7.0",
"clsx": "2.1.0",
Expand All @@ -34,17 +37,18 @@
"slugify": "1.6.6",
"tailwind-merge": "2.2.1",
"tailwindcss-animate": "1.0.7",
"use-debounce": "10.0.4",
"@tech-companies-portugal/analytics": "*"
"use-debounce": "10.0.4"
},
"devDependencies": {
"@tech-companies-portugal/typescript": "*",
"@playwright/test": "1.50.0",
"@tech-companies-portugal/tailwind": "*",
"@tech-companies-portugal/typescript": "*",
"@types/node": "^20",
"@types/react": "19.0.2",
"@types/react-dom": "19.0.2",
"autoprefixer": "^10.0.1",
"postcss": "^8",
"start-server-and-test": "2.0.10",
"tailwindcss": "^3.3.0",
"typescript": "^5"
},
Expand All @@ -53,4 +57,4 @@
"@types/react-dom": "19.0.2",
"react-is": "19.0.0"
}
}
}
86 changes: 86 additions & 0 deletions apps/web/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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",

timeout: 30_000,

expect: {
timeout: 15_000,
},

/* 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:3000",

/* 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: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
});
5 changes: 4 additions & 1 deletion apps/web/src/components/CompaniesHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { AnimatedCompaniesFeatures } from "./ui/AnimatedCompaniesFeatures";

export default function CompaniesHeader() {
return (
<section className="relative w-full overflow-hidden py-12 text-center">
<section
className="relative w-full overflow-hidden py-12 text-center"
data-testid="companies-header"
>
<div className="absolute inset-0 z-0 bg-background">
<Image
src={bgHeader}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/CompaniesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default function CompaniesList({
filteredCompanies={filteredCompanies}
/>
</motion.div>
<div className="flex-1 space-y-4">
<div className="flex-1 space-y-4" data-testid="companies-list">
{paginatedCompanies.map((company, index) => (
<motion.div
key={company.slug}
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/components/CompaniesListFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ export default function CompaniesListFooter({
const isNextDisabled = currentPage === totalPages;

return (
<div className="flex items-center justify-between gap-2">
<div
className="flex items-center justify-between gap-2"
data-testid="companies-list-footer"
>
<div className="flex basis-1/2 justify-end text-sm text-muted-foreground h-9">
<Badge variant="outline" className="rounded-none bg-white px-1 gap-1">
Page {currentPage} of {totalPages}
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/CompanyItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function CompanyItem({
<RetroContainer
variant={isFeatured ? "featured" : "default"}
className="w-full relative"
data-testid="company-item"
>
<Link
className={cn(
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { Button } from "./ui/button";

export default function Navbar() {
return (
<header className="bg-background shadow-sm sticky top-0 z-10 py-2 font-mono font-semibold">
<header
className="bg-background shadow-sm sticky top-0 z-10 py-2 font-mono font-semibold"
data-testid="navbar"
>
<div className="container mx-auto flex h-full items-center justify-between flex-wrap px-3">
<Link href="/" className="flex items-center gap-1 flex-shrink-0">
<Image
Expand Down
43 changes: 43 additions & 0 deletions apps/web/tests/homepage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { expect, test } from "@playwright/test";

test.describe("Homepage e2e tests", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
});

test("Check if all relevant elements are visible in the page", async ({
page,
}) => {
// check if navbar is visible
await expect(page.getByTestId("navbar")).toBeVisible();

// check if companies header is visible
await expect(page.getByTestId("companies-header")).toBeVisible();

// check if companies list is visible
await expect(page.getByTestId("companies-list")).toBeVisible();

// check if there are more than 1 company items
const companyItems = await page.getByTestId("company-item").all();
expect(companyItems.length).toBeGreaterThan(1);

// check if filters are visible and reset button is disabled
await expect(
page.getByRole("combobox", { name: "Category" }),
).toBeVisible();
await expect(
page.getByRole("textbox", { name: "Search term" }),
).toBeVisible();
await expect(
page.getByRole("combobox", { name: "Location" }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Reset filters" }),
).toBeDisabled();

// check if companies list footer is visible and has the correct text
const companiesListFooter = page.getByTestId("companies-list-footer");
await expect(companiesListFooter).toBeVisible();
await expect(companiesListFooter.getByText("Page 1 of")).toBeVisible();
});
});
Loading
Loading