test(e2e) : add Playwright setup with product and category CRUD specs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-02-13 09:07:23 +01:00
parent 62127a33f5
commit dbf8c8856b
7 changed files with 611 additions and 1 deletions

5
.gitignore vendored
View File

@@ -22,3 +22,8 @@ logs
.env
.env.*
!.env.example
# Playwright
e2e/.auth/
playwright-report/
test-results/

35
e2e/auth.setup.ts Normal file
View File

@@ -0,0 +1,35 @@
import { test as setup, expect } from '@playwright/test'
const AUTH_FILE = 'e2e/.auth/session.json'
/**
* Authentication setup: selects the first available profile
* to establish a session cookie before running any test.
*
* The app uses a profile-based session system:
* - GET /api/session/profiles → list available profiles
* - POST /api/session/profile → activate a profile (sets cookie)
*
* The global middleware (profile.global.ts) redirects to /profiles
* if no active profile is found.
*/
setup('select a profile to authenticate', async ({ page }) => {
// Go to the profiles page
await page.goto('/profiles')
// Wait for profiles to load
await expect(page.getByRole('heading', { name: 'Choisir un profil' })).toBeVisible({ timeout: 15_000 })
// Wait for at least one profile button to appear
const profileButton = page.locator('button.btn-outline').first()
await expect(profileButton).toBeVisible({ timeout: 10_000 })
// Click the first available profile
await profileButton.click()
// Wait for redirect to home page (profile selected → session cookie set)
await page.waitForURL('/', { timeout: 10_000 })
// Save authenticated state (cookies + localStorage)
await page.context().storageState({ path: AUTH_FILE })
})

View File

@@ -0,0 +1,166 @@
import { test, expect } from '@playwright/test'
/**
* E2E tests for Product Category CRUD operations.
*
* Prerequisites:
* - Frontend running on http://localhost:3001 (npm run dev)
* - Backend running on http://localhost:8081 (docker compose up)
* - Auth setup must run first (profile selected)
*/
const UNIQUE = Date.now()
const CATEGORY_NAME = `E2E Catégorie Produit ${UNIQUE}`
const CATEGORY_NOTES = `Notes de test automatisé ${UNIQUE}`
const CATEGORY_NAME_UPDATED = `${CATEGORY_NAME} modifié`
const CATEGORY_NOTES_UPDATED = `${CATEGORY_NOTES} — mis à jour`
test.describe('Product Category CRUD', () => {
test.describe.configure({ mode: 'serial' })
// ──────────────────────────────────────────────
// CREATE
// ──────────────────────────────────────────────
test('should display the product category list page', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('heading', { name: 'Catégories de produit' })).toBeVisible({ timeout: 10_000 })
await expect(page.getByText('Catégories enregistrées')).toBeVisible()
})
test('should navigate to the create form', async ({ page }) => {
await page.goto('/product-category')
// The toolbar button text is "Créer" (with a plus icon)
await page.getByRole('button', { name: /créer/i }).click()
await expect(page).toHaveURL('/product-category/new')
await expect(page.getByRole('heading', { name: 'Nouvelle catégorie de produit' })).toBeVisible()
})
test('should show validation error for short name', async ({ page }) => {
await page.goto('/product-category/new')
await page.locator('#model-type-name').fill('A')
// The form submit button in ModelTypeForm is also "Créer"
await page.locator('button[type="submit"]').click()
await expect(page.getByText('Le nom doit contenir au moins 2 caractères')).toBeVisible()
})
test('should create a new product category', async ({ page }) => {
await page.goto('/product-category/new')
await page.locator('#model-type-name').fill(CATEGORY_NAME)
await page.locator('#model-type-notes').fill(CATEGORY_NOTES)
// Verify category is locked to PRODUCT
const categorySelect = page.locator('#model-type-category')
await expect(categorySelect).toBeDisabled()
await expect(categorySelect).toHaveValue('PRODUCT')
await page.locator('button[type="submit"]').click()
// Should redirect to list and show success toast
await expect(page).toHaveURL('/product-category', { timeout: 10_000 })
await expect(page.getByText('Catégorie de produit créée avec succès')).toBeVisible()
})
// ──────────────────────────────────────────────
// READ
// ──────────────────────────────────────────────
test('should display the created category in the list', async ({ page }) => {
await page.goto('/product-category')
// Target the table cell specifically (desktop view also renders a mobile card)
await expect(page.getByRole('cell', { name: CATEGORY_NAME })).toBeVisible({ timeout: 10_000 })
})
test('should find the category via search', async ({ page }) => {
await page.goto('/product-category')
// Type in search input (placeholder: "Rechercher par nom…")
const searchInput = page.getByPlaceholder('Rechercher par nom…')
await searchInput.fill(UNIQUE.toString())
// Wait for debounce (300ms) + API response
await page.waitForTimeout(500)
await expect(page.getByRole('cell', { name: CATEGORY_NAME })).toBeVisible({ timeout: 5_000 })
})
// ──────────────────────────────────────────────
// UPDATE
// ──────────────────────────────────────────────
test('should navigate to the edit page', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('cell', { name: CATEGORY_NAME })).toBeVisible({ timeout: 10_000 })
// Find the row with our category and click "Éditer"
const row = page.getByRole('row').filter({ hasText: CATEGORY_NAME })
await row.getByRole('button', { name: 'Éditer' }).click()
await expect(page.getByRole('heading', { name: /modifier/i })).toBeVisible({ timeout: 10_000 })
})
test('should edit the category name and notes', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('cell', { name: CATEGORY_NAME })).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: CATEGORY_NAME })
await row.getByRole('button', { name: 'Éditer' }).click()
await expect(page.getByRole('heading', { name: /modifier/i })).toBeVisible({ timeout: 10_000 })
// Update name
const nameInput = page.locator('#model-type-name')
await nameInput.clear()
await nameInput.fill(CATEGORY_NAME_UPDATED)
// Update notes
const notesTextarea = page.locator('#model-type-notes')
await notesTextarea.clear()
await notesTextarea.fill(CATEGORY_NOTES_UPDATED)
await page.locator('button[type="submit"]').click()
// Should redirect and show success
await expect(page).toHaveURL('/product-category', { timeout: 10_000 })
await expect(page.getByText('Catégorie de produit mise à jour avec succès')).toBeVisible()
})
test('should display updated category in the list', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('cell', { name: CATEGORY_NAME_UPDATED })).toBeVisible({ timeout: 10_000 })
})
// ──────────────────────────────────────────────
// DELETE
// ──────────────────────────────────────────────
test('should cancel deletion when clicking Annuler', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('cell', { name: CATEGORY_NAME_UPDATED })).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: CATEGORY_NAME_UPDATED })
await row.getByRole('button', { name: 'Supprimer' }).click()
// Confirmation modal should appear
await expect(page.getByText('Supprimer ce type ?')).toBeVisible()
await page.getByRole('button', { name: 'Annuler' }).click()
// Category should still be present
await expect(page.getByRole('cell', { name: CATEGORY_NAME_UPDATED })).toBeVisible()
})
test('should delete the category', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('cell', { name: CATEGORY_NAME_UPDATED })).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: CATEGORY_NAME_UPDATED })
await row.getByRole('button', { name: 'Supprimer' }).click()
// Confirm deletion in modal
await expect(page.getByText('Supprimer ce type ?')).toBeVisible()
// Click the confirm "Supprimer" button inside the modal (btn-error style)
await page.locator('button.btn-error').filter({ hasText: 'Supprimer' }).click()
// Should show success toast and category should disappear
await expect(page.getByText(/supprimé avec succès/i)).toBeVisible({ timeout: 10_000 })
await expect(page.getByRole('cell', { name: CATEGORY_NAME_UPDATED })).not.toBeVisible()
})
})

299
e2e/product-crud.spec.ts Normal file
View File

@@ -0,0 +1,299 @@
import { test, expect, type Page } from '@playwright/test'
/**
* E2E tests for Product CRUD operations.
*
* Prerequisites:
* - Frontend running on http://localhost:3001 (npm run dev)
* - Backend running on http://localhost:8081 (docker compose up)
* - Auth setup must run first (profile selected)
*
* These tests create a temporary product category, use it to test
* the full product CRUD, then clean up both.
*/
const UNIQUE = Date.now()
const TEST_CATEGORY_NAME = `E2E Cat Produit ${UNIQUE}`
const PRODUCT_NAME = `E2E Produit Test ${UNIQUE}`
const PRODUCT_REFERENCE = `REF-E2E-${UNIQUE}`
const PRODUCT_PRICE = '42.50'
const PRODUCT_NAME_UPDATED = `${PRODUCT_NAME} modifié`
const PRODUCT_REFERENCE_UPDATED = `${PRODUCT_REFERENCE}-UPD`
const PRODUCT_PRICE_UPDATED = '99.99'
// ──────────────────────────────────────────────
// Helpers
// ──────────────────────────────────────────────
/**
* Creates a product category via the UI.
*/
async function createTestCategory(page: Page) {
await page.goto('/product-category/new')
await page.locator('#model-type-name').fill(TEST_CATEGORY_NAME)
await page.locator('button[type="submit"]').click()
await expect(page).toHaveURL('/product-category', { timeout: 10_000 })
await expect(page.getByText('Catégorie de produit créée avec succès')).toBeVisible()
}
/**
* Selects an option in a SearchSelect component.
*
* The SearchSelect renders:
* .search-select > .relative > input[placeholder]
* .search-select > .relative > div (dropdown) > ul > li > button
*/
async function selectSearchOption(page: Page, placeholder: string, searchText: string) {
const input = page.getByPlaceholder(placeholder)
await input.click()
await input.fill(searchText)
// The dropdown is inside .search-select > .relative > div > ul > li > button
const option = page.locator('.search-select ul li button')
.filter({ hasText: searchText })
.first()
await option.waitFor({ state: 'visible', timeout: 10_000 })
await option.click()
}
/**
* Cleans up test data: deletes the category via the UI.
*/
async function cleanupTestCategory(page: Page) {
await page.goto('/product-category')
// Wait for list to load
await page.waitForTimeout(1_000)
const row = page.getByRole('row').filter({ hasText: TEST_CATEGORY_NAME })
if (await row.isVisible().catch(() => false)) {
await row.getByRole('button', { name: 'Supprimer' }).click()
const confirmBtn = page.locator('button.btn-error').filter({ hasText: 'Supprimer' })
await confirmBtn.waitFor({ state: 'visible', timeout: 5_000 })
await confirmBtn.click()
await page.waitForTimeout(1_000)
}
}
test.describe('Product CRUD', () => {
test.describe.configure({ mode: 'serial' })
// ──────────────────────────────────────────────
// SETUP: Create a test category
// ──────────────────────────────────────────────
test('setup: create a test product category', async ({ page }) => {
await createTestCategory(page)
})
// ──────────────────────────────────────────────
// LIST PAGE
// ──────────────────────────────────────────────
test('should display the product catalog page', async ({ page }) => {
await page.goto('/product-catalog')
await expect(page.getByRole('heading', { name: 'Catalogue des produits' })).toBeVisible({ timeout: 10_000 })
await expect(page.getByRole('link', { name: /ajouter un produit/i })).toBeVisible()
await expect(page.getByRole('link', { name: /gérer les catégories/i })).toBeVisible()
})
test('should navigate to create product page', async ({ page }) => {
await page.goto('/product-catalog')
await page.getByRole('link', { name: /ajouter un produit/i }).click()
await expect(page).toHaveURL('/product/create')
await expect(page.getByRole('heading', { name: 'Nouveau produit' })).toBeVisible()
})
// ──────────────────────────────────────────────
// CREATE
// ──────────────────────────────────────────────
test('should show disabled fields until category is selected', async ({ page }) => {
await page.goto('/product/create')
// Name input should be disabled before selecting a category
const nameInput = page.getByPlaceholder('Nom affiché dans le catalogue')
await expect(nameInput).toBeDisabled()
})
test('should create a product with all fields', async ({ page }) => {
await page.goto('/product/create')
// 1. Select category via SearchSelect
await selectSearchOption(page, 'Rechercher une catégorie...', TEST_CATEGORY_NAME)
// Wait for form to enable after category selection
const nameInput = page.getByPlaceholder('Nom affiché dans le catalogue')
await expect(nameInput).toBeEnabled({ timeout: 5_000 })
// 2. Fill form fields
await nameInput.clear()
await nameInput.fill(PRODUCT_NAME)
const referenceInput = page.getByPlaceholder('Référence interne ou fournisseur')
await referenceInput.fill(PRODUCT_REFERENCE)
const priceInput = page.getByPlaceholder('Valeur indicatrice')
await priceInput.fill(PRODUCT_PRICE)
// 3. Submit
await page.getByRole('button', { name: /créer le produit/i }).click()
// Should redirect to catalog and show success
await expect(page).toHaveURL('/product-catalog', { timeout: 15_000 })
await expect(page.getByText('Produit créé avec succès')).toBeVisible()
})
// ──────────────────────────────────────────────
// READ
// ──────────────────────────────────────────────
test('should display the created product in the catalog', async ({ page }) => {
await page.goto('/product-catalog')
await expect(page.getByText(PRODUCT_NAME)).toBeVisible({ timeout: 10_000 })
})
test('should show product reference in the catalog', async ({ page }) => {
await page.goto('/product-catalog')
await expect(page.getByText(PRODUCT_REFERENCE)).toBeVisible({ timeout: 10_000 })
})
test('should find the product via search', async ({ page }) => {
await page.goto('/product-catalog')
const searchInput = page.getByPlaceholder('Nom ou référence…')
await searchInput.fill(PRODUCT_REFERENCE)
// Wait for client-side filtering
await page.waitForTimeout(300)
await expect(page.getByText(PRODUCT_NAME)).toBeVisible()
await expect(page.getByText(PRODUCT_REFERENCE)).toBeVisible()
})
test('should show category link in the catalog', async ({ page }) => {
await page.goto('/product-catalog')
await expect(page.getByText(PRODUCT_NAME)).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: PRODUCT_NAME })
await expect(row.getByText(TEST_CATEGORY_NAME)).toBeVisible()
})
test('should sort products by name', async ({ page }) => {
await page.goto('/product-catalog')
await page.locator('#product-sort').selectOption('name')
await page.locator('#product-dir').selectOption('asc')
await expect(page.getByRole('heading', { name: 'Catalogue des produits' })).toBeVisible()
})
test('should sort products by creation date', async ({ page }) => {
await page.goto('/product-catalog')
await page.locator('#product-sort').selectOption('createdAt')
await page.locator('#product-dir').selectOption('desc')
await expect(page.getByRole('heading', { name: 'Catalogue des produits' })).toBeVisible()
})
// ──────────────────────────────────────────────
// UPDATE
// ──────────────────────────────────────────────
test('should navigate to edit page from catalog', async ({ page }) => {
await page.goto('/product-catalog')
await expect(page.getByText(PRODUCT_NAME)).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: PRODUCT_NAME })
await row.getByRole('link', { name: 'Modifier' }).click()
await expect(page.getByRole('heading', { name: 'Modifier le produit' })).toBeVisible({ timeout: 10_000 })
})
test('should show category note on edit page', async ({ page }) => {
await page.goto('/product-catalog')
await expect(page.getByText(PRODUCT_NAME)).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: PRODUCT_NAME })
await row.getByRole('link', { name: 'Modifier' }).click()
await expect(page.getByRole('heading', { name: 'Modifier le produit' })).toBeVisible({ timeout: 10_000 })
// Category should be displayed but disabled
await expect(page.getByText("La catégorie d'origine ne peut pas être modifiée")).toBeVisible()
})
test('should update the product', async ({ page }) => {
await page.goto('/product-catalog')
await expect(page.getByText(PRODUCT_NAME)).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: PRODUCT_NAME })
await row.getByRole('link', { name: 'Modifier' }).click()
await expect(page.getByRole('heading', { name: 'Modifier le produit' })).toBeVisible({ timeout: 10_000 })
// Update name (label-text "Nom du produit" → sibling input)
const nameInput = page.locator('.form-control').filter({ hasText: 'Nom du produit' }).locator('input')
await nameInput.clear()
await nameInput.fill(PRODUCT_NAME_UPDATED)
// Update reference
const refInput = page.locator('.form-control').filter({ hasText: 'Référence' }).locator('input')
await refInput.clear()
await refInput.fill(PRODUCT_REFERENCE_UPDATED)
// Update price
const priceInput = page.locator('.form-control').filter({ hasText: 'Prix fournisseur' }).locator('input')
await priceInput.clear()
await priceInput.fill(PRODUCT_PRICE_UPDATED)
// Submit
await page.getByRole('button', { name: /enregistrer les modifications/i }).click()
await expect(page).toHaveURL('/product-catalog', { timeout: 10_000 })
await expect(page.getByText('Produit mis à jour avec succès')).toBeVisible()
})
test('should display updated product in catalog', async ({ page }) => {
await page.goto('/product-catalog')
await expect(page.getByText(PRODUCT_NAME_UPDATED)).toBeVisible({ timeout: 10_000 })
await expect(page.getByText(PRODUCT_REFERENCE_UPDATED)).toBeVisible()
})
// ──────────────────────────────────────────────
// DELETE
// ──────────────────────────────────────────────
test('should cancel product deletion', async ({ page }) => {
await page.goto('/product-catalog')
await expect(page.getByText(PRODUCT_NAME_UPDATED)).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: PRODUCT_NAME_UPDATED })
await row.getByRole('button', { name: 'Supprimer' }).click()
// Confirmation modal
await expect(page.getByText(/voulez-vous vraiment supprimer/i)).toBeVisible()
await page.getByRole('button', { name: 'Annuler' }).click()
// Product should still be here
await expect(page.getByText(PRODUCT_NAME_UPDATED)).toBeVisible()
})
test('should delete the product', async ({ page }) => {
await page.goto('/product-catalog')
await expect(page.getByText(PRODUCT_NAME_UPDATED)).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: PRODUCT_NAME_UPDATED })
await row.getByRole('button', { name: 'Supprimer' }).click()
// Confirm deletion in modal
await expect(page.getByText(/voulez-vous vraiment supprimer/i)).toBeVisible()
await page.locator('button.btn-error').filter({ hasText: 'Supprimer' }).click()
await expect(page.getByText(/supprimé/i)).toBeVisible({ timeout: 10_000 })
// The toast message contains the product name, so check the table specifically
const table = page.locator('table')
await expect(table.getByText(PRODUCT_NAME_UPDATED)).not.toBeVisible({ timeout: 5_000 })
})
// ──────────────────────────────────────────────
// CLEANUP: Remove the test category
// ──────────────────────────────────────────────
test('cleanup: delete the test product category', async ({ page }) => {
await cleanupTestCategory(page)
})
})

64
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"devDependencies": {
"@iconify-json/lucide": "^1.2.68",
"@nuxt/eslint-config": "^1.9.0",
"@playwright/test": "^1.58.2",
"@rushstack/eslint-patch": "^1.12.0",
"@types/node": "^25.2.1",
"@typescript-eslint/eslint-plugin": "^8.44.1",
@@ -3202,6 +3203,22 @@
"node": ">=14"
}
},
"node_modules/@playwright/test": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.58.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@polka/url": {
"version": "1.0.0-next.29",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
@@ -10828,6 +10845,53 @@
"pathe": "^2.0.3"
}
},
"node_modules/playwright": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.58.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/pluralize": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",

View File

@@ -12,7 +12,10 @@
"lint": "eslint . --ext .js,.ts,.vue",
"lint:fix": "npm run lint -- --fix",
"test": "vitest run",
"test:watch": "vitest"
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:headed": "playwright test --headed"
},
"dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",
@@ -26,6 +29,7 @@
"devDependencies": {
"@iconify-json/lucide": "^1.2.68",
"@nuxt/eslint-config": "^1.9.0",
"@playwright/test": "^1.58.2",
"@rushstack/eslint-patch": "^1.12.0",
"@types/node": "^25.2.1",
"@typescript-eslint/eslint-plugin": "^8.44.1",

37
playwright.config.ts Normal file
View File

@@ -0,0 +1,37 @@
import { defineConfig, devices } from '@playwright/test'
const AUTH_FILE = 'e2e/.auth/session.json'
export default defineConfig({
testDir: './e2e',
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: 1,
reporter: 'html',
timeout: 30_000,
use: {
baseURL: process.env.E2E_BASE_URL || 'http://localhost:3001',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
// Auth setup: selects a profile to get a session cookie
{
name: 'auth-setup',
testMatch: /auth\.setup\.ts/,
},
// All tests run after auth setup, with the saved session
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: AUTH_FILE,
},
dependencies: ['auth-setup'],
},
],
})