Skip to content
Draft
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
31 changes: 31 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Playwright Tests

on:
pull_request:
branches: [main] # Corre cuando el PR apunta a main
push:
branches: [draft/playwright-wip] # Opcional: corre en cada push a tu rama de trabajo
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm

- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test --reporter=html

- name: Upload Playwright HTML report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report
retention-days: 7
40 changes: 40 additions & 0 deletions api.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { defineConfig, devices } from "@playwright/test";
import "dotenv/config";
const isCI = !!process.env.CI;

export default defineConfig({
testDir: "./src/test/API",
fullyParallel: true,
forbidOnly: isCI,
retries: isCI ? 2 : 0,
workers: isCI ? 1 : undefined,
reporter: isCI
? [["html", { open: "never" }], ["list"]]
: [["html", { open: "never" }]],

expect: {
timeout: 5_000,
},

use: {
baseURL: process.env.BASE_URL || "https://automationintesting.online",
headless: true,
actionTimeout: 0,
navigationTimeout: 30_000,
trace: "on-first-retry",
video: "retain-on-failure",
screenshot: "only-on-failure",
serviceWorkers: "block",
},

projects: [
// Desktop
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
{ name: "webkit", use: { ...devices["Desktop Safari"] } },

// Mobile emulation
{ name: "Mobile Chrome", use: { ...devices["Pixel 7"] } },
{ name: "Mobile Safari", use: { ...devices["iPhone 14"] } },
],
});
10 changes: 10 additions & 0 deletions nonfunctional/performance/api_k6/env_local.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@echo off
set API_BOOKER_BAT_URL=https://restful-booker.herokuapp.com
set API_BOOKER_BAT_USERNAME=admin
set API_BOOKER_BAT_PASSWORD=password123
k6 run smoke_test.js
pause

echo URL=%API_BOOKER_BAT_URL%
echo USER=%API_BOOKER_BAT_USERNAME%
echo PASS=%API_BOOKER_BAT_PASSWORD%
163 changes: 163 additions & 0 deletions nonfunctional/performance/api_k6/load_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// init context: importing modules
import http from 'k6/http';
import { sleep } from 'k6'; //sleep lo usamos solo si quieres simular respiritos entre pasos
import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.4/index.js';
import { htmlReport } from 'https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js';


// init context: define k6 options
export const options = {
scenarios: {
load_e2e_booking: {
executor: 'constant-arrival-rate',
rate: 3, // 3 solicitudes por segundo (conservador)
timeUnit: '1s',
duration: '12m', // sostén
preAllocatedVUs: 10, // VUs que K6 reserva para cumplir la tasa
maxVUs: 20, // tope por si hace falta más
exec: 'default',
tags: { test_type: 'load', flow: 'e2e_booking' },
},
warmup: {
executor: 'ramping-arrival-rate',
startRate: 1,
timeUnit: '1s',
preAllocatedVUs: 5,
maxVUs: 10,
stages: [
{ duration: '2m', target: 3 }, // sube de 1 → 3 req/s
],
exec: 'default',
tags: { test_type: 'warmup', flow: 'e2e_booking' },
},
},
thresholds: {
checks: ['rate>0.99'],
http_req_duration: ['p(95)<800'],
http_req_failed: ['rate<0.01'],
},
};

// 1. init code - La idea: en setup() obtienes el token una sola vez y devuelves todo lo que vas a reutilizar en default().
export function setup() {
// A) Variables de entorno
const baseurl = __ENV.API_BOOKER_BAT_URL;
const username = __ENV.API_BOOKER_BAT_USERNAME;
const password = __ENV.API_BOOKER_BAT_PASSWORD;

if (!baseurl || !username || !password) {
throw new Error('Faltan variables de entorno: API_BOOKER_BAT_URL, API_BOOKER_BAT_USERNAME, API_BOOKER_BAT_PASSWORD');
}

// B) Headers JSON comunes
const jsonheaders = {
'Content-Type': 'application/json',
};

// C) Login y obtención del token
const createtoken = http.post(`${baseurl}/auth`, // Llama al endpoint con verbo POST y crea una URL de manera dinámica
JSON.stringify({ username: username, password: password }), // Cuerpo de la petición, convierte tu objeto JS a texto plano JSON (el formato que espera la API)
{ headers: jsonheaders } // Envia encabezados (headers), aquí el Content-Type
);
const token = createtoken.json('token'); // Extrae el token de la respuesta JSON

return {
token,
baseurl
};

}
// 2. setup code
export default function (data) {

//Post - Crea un booking
const baseurl = __ENV.API_BOOKER_BAT_URL;
const baseurlpost =`${baseurl}/booking`;
const jsonpostheaders = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
const payload = JSON.stringify({
firstname: `smoke_${Date.now()}`,
lastname: 'Brown',
totalprice: 111,
depositpaid: true,
bookingdates: { checkin: '2018-01-01', checkout: '2019-01-01' },
additionalneeds: 'Breakfast',
});

const createbooking = http.post(baseurlpost, payload, { headers: jsonpostheaders });
const bookingidnew = createbooking.json('bookingid');
sleep(1); // Simula un tiempo de espera entre acciones (opcional)


// PATCH - Actualiza el nombre del booking creado
const baseurlpatch =`${baseurl}/booking/${bookingidnew}`;
const tokenpatch = data.token; // Obtén el token del objeto data pasado desde setup()
const jsonpatchheaders = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Cookie': `token=${tokenpatch}`, // Usa el token obtenido en setup()
};
const payloadpatch = JSON.stringify({
firstname: `smoke_${Date.now()}`, // Actualiza el nombre con un valor único
lastname: 'Fernandez', // Actualiza el apellido
});

const updatebooking = http.patch(baseurlpatch, payloadpatch, { headers: jsonpatchheaders });
sleep(1); // Simula un tiempo de espera entre acciones (opcional)


//PUT - Actualiza todo el booking creado
const baseurlput =`${baseurl}/booking/${bookingidnew}`;
const tokenput = data.token; // Obtén el token del objeto data pasado desde setup()
const payloadput = JSON.stringify({
"firstname" : "James",
"lastname" : "Smith",
"totalprice" : 111,
"depositpaid" : true,
"bookingdates" : {
"checkin" : "2018-01-01",
"checkout" : "2019-01-01"
},
"additionalneeds" : "Breakfast"
});
const jsonputheaders = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Cookie': `token=${tokenput}`, // Usa el token obtenido en setup()
};

const putbooking = http.put(baseurlput, payloadput, { headers: jsonputheaders });
sleep(1); // Simula un tiempo de espera entre acciones (opcional)


//DELETE - Elimina el booking creado
const urldelete = `${baseurl}/booking/${bookingidnew}`;
const tokendelete = data.token; // Obtén el token del objeto data pasado desde setup()
const jsondeleteheaders = {
'Accept': 'application/json',
'Cookie': `token=${tokendelete}`, // Usa el token obtenido en setup()
};

const deletebooking = http.del(urldelete, null, { headers: jsondeleteheaders });
sleep(1); // Simula un tiempo de espera entre acciones (opcional)


//GET - Ping - HealthCheck
const urlgetping = `${baseurl}/ping`;
const getping = http.get(urlgetping);
sleep(1); // Simula un tiempo de espera entre acciones (opcional)
}

export function handleSummary(data) {
return {
'stdout': textSummary(data, { indent: ' ', enableColors: true }),
'summary.json': JSON.stringify(data, null, 2),
'summary.html': htmlReport(data),
};
}

export function teardown() {
console.log('Teardown: prueba finalizada.');
}
Loading
Loading