Initial commit

This commit is contained in:
pigui
2026-08-30 21:11:59 +02:00
commit 3f930bb46a
168 changed files with 35708 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
import playwright from "eslint-plugin-playwright";
import baseConfig from "../eslint.config.mjs";
export default [
playwright.configs["flat/recommended"],
...baseConfig,
{
files: [
"**/*.ts",
"**/*.js"
],
// Override or add rules here
rules: {}
}
];

View File

@@ -0,0 +1,40 @@
import { defineConfig, devices } from '@playwright/test';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
/**
* Written without `@nx/playwright`'s preset on purpose: Playwright loads this
* file through Node's ESM loader, and pulling in `@nx/devkit` from there drags
* Nx's native binding in under `runSync`, where it fails to initialise. Only
* `testDir`, `outputDir` and the web server came from the preset anyway.
*/
const here = dirname(fileURLToPath(import.meta.url));
const workspaceRoot = resolve(here, '..');
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
export default defineConfig({
testDir: './src',
outputDir: resolve(workspaceRoot, 'dist/.playwright/landing-e2e/test-output'),
timeout: 60_000,
forbidOnly: !!process.env['CI'],
retries: process.env['CI'] ? 2 : 0,
workers: process.env['CI'] ? 1 : undefined,
reporter: [['list']],
use: {
baseURL,
trace: 'on-first-retry',
},
webServer: {
command: 'npx nx run landing:serve',
url: baseURL,
reuseExistingServer: true,
timeout: 180_000,
cwd: workspaceRoot,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});

11
landing-e2e/project.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "landing-e2e",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "landing-e2e/src",
"implicitDependencies": [
"landing"
],
"// targets": "to see all targets run: nx show project landing-e2e --web",
"targets": {}
}

View File

@@ -0,0 +1,66 @@
import { expect, test } from '@playwright/test';
test.describe('landing', () => {
test('renders the hero server-side', async ({ page }) => {
const response = await page.goto('/');
expect(response?.status()).toBe(200);
await expect(page.locator('h1')).toContainText('Construimos software');
});
test('serves the page in the language the URL asks for', async ({ page }) => {
await page.goto('/?lang=en');
await expect(page.locator('h1')).toContainText('We build software');
await expect(page.locator('html')).toHaveAttribute('lang', 'en');
});
test('server-renders the whole page, not just the top of it', async ({
browser,
}) => {
// With JavaScript off, what is left is exactly what the server sent. The
// check is the last section and the footer: a component that throws during
// rendering does not merely lose itself, it abandons everything after it,
// and the page still answers 200 while quietly ending early. That is how
// `pi-carousel` reaching for `document` once cost this page its contact
// form server-side.
const context = await browser.newContext({ javaScriptEnabled: false });
const page = await context.newPage();
await page.goto('/');
await expect(page.locator('h1')).toContainText('Construimos software');
await expect(page.locator('#contacto')).toBeAttached();
await expect(page.getByRole('contentinfo')).toContainText('Pireon');
await context.close();
});
test('answers 404 for an address it does not serve', async ({ page }) => {
const response = await page.goto('/no-existe');
expect(response?.status()).toBe(404);
await expect(page.locator('h1')).toContainText('no existe');
});
test('refuses an incomplete contact submission without calling the API', async ({
page,
}) => {
await page.goto('/');
await page.locator('#contacto').scrollIntoViewIfNeeded();
const before = page.url();
const submit = page.getByRole('button', { name: 'Enviar' });
// Enabled only once hydration attached the handler — before that a click
// would submit natively and navigate away with the message. The generous
// timeout is for the dev server's first compile, not for hydration.
await expect(submit).toBeEnabled({ timeout: 30_000 });
await submit.click();
// Nothing navigates and nothing is sent: the form holds the visitor there
// rather than spending one of their throttled attempts on a bad payload.
await expect(submit).toBeVisible();
expect(page.url()).toBe(before);
});
});

24
landing-e2e/tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"allowJs": true,
"outDir": "../dist/out-tsc",
"sourceMap": false,
"module": "commonjs",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"**/*.ts",
"**/*.js",
"playwright.config.mts",
"src/**/*.spec.ts",
"src/**/*.spec.js",
"src/**/*.test.ts",
"src/**/*.test.js",
"src/**/*.d.ts"
]
}