mirror of
https://github.com/umami-software/umami.git
synced 2026-05-30 06:47:25 +00:00
Migrate test suite from Cypress and Jest to Playwright and Vitest
This commit is contained in:
@@ -9,6 +9,8 @@ package-lock.json
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
/playwright-report
|
||||
/test-results
|
||||
|
||||
# next.js
|
||||
next-env.d.ts
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:3000',
|
||||
},
|
||||
// default username / password on init
|
||||
env: {
|
||||
umami_user: 'admin',
|
||||
umami_password: 'umami',
|
||||
umami_user_id: '41e2b680-648e-4b09-bcd7-3e2b10c06264',
|
||||
},
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
version: '3'
|
||||
services:
|
||||
umami:
|
||||
build: ../
|
||||
#image: ghcr.io/umami-software/umami:postgresql-latest
|
||||
ports:
|
||||
- '3000:3000'
|
||||
environment:
|
||||
DATABASE_URL: postgresql://umami:umami@db:5432/umami
|
||||
DATABASE_TYPE: postgresql
|
||||
APP_SECRET: replace-me-with-a-random-string
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'curl http://localhost:3000/api/heartbeat']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
POSTGRES_DB: umami
|
||||
POSTGRES_USER: umami
|
||||
POSTGRES_PASSWORD: umami
|
||||
volumes:
|
||||
- umami-db-data:/var/lib/postgresql/data
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
cypress:
|
||||
image: 'cypress/included:13.6.0'
|
||||
depends_on:
|
||||
- umami
|
||||
- db
|
||||
environment:
|
||||
- CYPRESS_baseUrl=http://umami:3000
|
||||
- CYPRESS_umami_user=admin
|
||||
- CYPRESS_umami_password=umami
|
||||
volumes:
|
||||
- ./tsconfig.json:/tsconfig.json
|
||||
- ../cypress.config.ts:/cypress.config.ts
|
||||
- ./:/cypress
|
||||
- ../node_modules/:/node_modules
|
||||
- ../src/lib/crypto.ts:/src/lib/crypto.ts
|
||||
volumes:
|
||||
umami-db-data:
|
||||
@@ -1,209 +0,0 @@
|
||||
describe('Team API tests', () => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
||||
let teamId;
|
||||
let userId;
|
||||
|
||||
before(() => {
|
||||
cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
|
||||
cy.fixture('users').then(data => {
|
||||
const userCreate = data.userCreate;
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/users',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: userCreate,
|
||||
}).then(response => {
|
||||
userId = response.body.id;
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('username', 'cypress1');
|
||||
expect(response.body).to.have.property('role', 'user');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Creates a team.', () => {
|
||||
cy.fixture('teams').then(data => {
|
||||
const teamCreate = data.teamCreate;
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/teams',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: teamCreate,
|
||||
}).then(response => {
|
||||
teamId = response.body[0].id;
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body[0]).to.have.property('name', 'cypress');
|
||||
expect(response.body[1]).to.have.property('role', 'team-owner');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Gets a teams by ID.', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/teams/${teamId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('id', teamId);
|
||||
});
|
||||
});
|
||||
|
||||
it('Updates a team.', () => {
|
||||
cy.fixture('teams').then(data => {
|
||||
const teamUpdate = data.teamUpdate;
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `/api/teams/${teamId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: teamUpdate,
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('id', teamId);
|
||||
expect(response.body).to.have.property('name', 'cypressUpdate');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Get all users that belong to a team.', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/teams/${teamId}/users`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body.data[0]).to.have.property('id');
|
||||
expect(response.body.data[0]).to.have.property('teamId');
|
||||
expect(response.body.data[0]).to.have.property('userId');
|
||||
expect(response.body.data[0]).to.have.property('user');
|
||||
});
|
||||
});
|
||||
|
||||
it('Get a user belonging to a team.', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/teams/${teamId}/users/${Cypress.env('umami_user_id')}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('teamId');
|
||||
expect(response.body).to.have.property('userId');
|
||||
expect(response.body).to.have.property('role');
|
||||
});
|
||||
});
|
||||
|
||||
it('Get all websites belonging to a team.', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/teams/${teamId}/websites`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('data');
|
||||
});
|
||||
});
|
||||
|
||||
it('Add a user to a team.', () => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `/api/teams/${teamId}/users`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: {
|
||||
userId,
|
||||
role: 'team-member',
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('userId', userId);
|
||||
expect(response.body).to.have.property('role', 'team-member');
|
||||
});
|
||||
});
|
||||
|
||||
it(`Update a user's role on a team.`, () => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `/api/teams/${teamId}/users/${userId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: {
|
||||
role: 'team-view-only',
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('userId', userId);
|
||||
expect(response.body).to.have.property('role', 'team-view-only');
|
||||
});
|
||||
});
|
||||
|
||||
it(`Remove a user from a team.`, () => {
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/teams/${teamId}/users/${userId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
|
||||
it('Deletes a team.', () => {
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/teams/${teamId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('ok', true);
|
||||
});
|
||||
});
|
||||
|
||||
// it('Gets all teams that belong to a user.', () => {
|
||||
// cy.request({
|
||||
// method: 'GET',
|
||||
// url: `/api/users/${userId}/teams`,
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// Authorization: Cypress.env('authorization'),
|
||||
// },
|
||||
// }).then(response => {
|
||||
// expect(response.status).to.eq(200);
|
||||
// expect(response.body).to.have.property('data');
|
||||
// });
|
||||
// });
|
||||
|
||||
after(() => {
|
||||
cy.deleteUser(userId);
|
||||
});
|
||||
});
|
||||
@@ -1,125 +0,0 @@
|
||||
describe('User API tests', () => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
||||
before(() => {
|
||||
cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
|
||||
});
|
||||
|
||||
let userId;
|
||||
|
||||
it('Creates a user.', () => {
|
||||
cy.fixture('users').then(data => {
|
||||
const userCreate = data.userCreate;
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/users',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: userCreate,
|
||||
}).then(response => {
|
||||
userId = response.body.id;
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('username', 'cypress1');
|
||||
expect(response.body).to.have.property('role', 'user');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns all users. Admin access is required.', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: '/api/admin/users',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body.data[0]).to.have.property('id');
|
||||
expect(response.body.data[0]).to.have.property('username');
|
||||
expect(response.body.data[0]).to.have.property('password');
|
||||
expect(response.body.data[0]).to.have.property('role');
|
||||
});
|
||||
});
|
||||
|
||||
it('Updates a user.', () => {
|
||||
cy.fixture('users').then(data => {
|
||||
const userUpdate = data.userUpdate;
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `/api/users/${userId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: userUpdate,
|
||||
}).then(response => {
|
||||
userId = response.body.id;
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('id', userId);
|
||||
expect(response.body).to.have.property('username', 'cypress1');
|
||||
expect(response.body).to.have.property('role', 'view-only');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Gets a user by ID.', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/users/${userId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('id', userId);
|
||||
expect(response.body).to.have.property('username', 'cypress1');
|
||||
expect(response.body).to.have.property('role', 'view-only');
|
||||
});
|
||||
});
|
||||
|
||||
it('Deletes a user.', () => {
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/users/${userId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('ok', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('Gets all websites that belong to a user.', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/users/${userId}/websites`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('data');
|
||||
});
|
||||
});
|
||||
|
||||
it('Gets all teams that belong to a user.', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/users/${userId}/teams`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('data');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,198 +0,0 @@
|
||||
import { uuid } from '../../src/lib/crypto';
|
||||
|
||||
describe('Website API tests', () => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
||||
let websiteId;
|
||||
let teamId;
|
||||
|
||||
before(() => {
|
||||
cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
|
||||
cy.fixture('teams').then(data => {
|
||||
const teamCreate = data.teamCreate;
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/teams',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: teamCreate,
|
||||
}).then(response => {
|
||||
teamId = response.body[0].id;
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body[0]).to.have.property('name', 'cypress');
|
||||
expect(response.body[1]).to.have.property('role', 'team-owner');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Creates a website for user.', () => {
|
||||
cy.fixture('websites').then(data => {
|
||||
const websiteCreate = data.websiteCreate;
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/websites',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: websiteCreate,
|
||||
}).then(response => {
|
||||
websiteId = response.body.id;
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('name', 'Cypress Website');
|
||||
expect(response.body).to.have.property('domain', 'cypress.com');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Creates a website for team.', () => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/websites',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: {
|
||||
name: 'Team Website',
|
||||
domain: 'teamwebsite.com',
|
||||
teamId: teamId,
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('name', 'Team Website');
|
||||
expect(response.body).to.have.property('domain', 'teamwebsite.com');
|
||||
});
|
||||
});
|
||||
|
||||
it('Creates a website with a fixed ID.', () => {
|
||||
cy.fixture('websites').then(data => {
|
||||
const websiteCreate = data.websiteCreate;
|
||||
const fixedId = uuid();
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/websites',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: { ...websiteCreate, id: fixedId },
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('id', fixedId);
|
||||
expect(response.body).to.have.property('name', 'Cypress Website');
|
||||
expect(response.body).to.have.property('domain', 'cypress.com');
|
||||
|
||||
// cleanup
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/websites/${fixedId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns all tracked websites.', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: '/api/websites',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body.data[0]).to.have.property('id');
|
||||
expect(response.body.data[0]).to.have.property('name');
|
||||
expect(response.body.data[0]).to.have.property('domain');
|
||||
});
|
||||
});
|
||||
|
||||
it('Gets a website by ID.', () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/websites/${websiteId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('name', 'Cypress Website');
|
||||
expect(response.body).to.have.property('domain', 'cypress.com');
|
||||
});
|
||||
});
|
||||
|
||||
it('Updates a website.', () => {
|
||||
cy.fixture('websites').then(data => {
|
||||
const websiteUpdate = data.websiteUpdate;
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `/api/websites/${websiteId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: websiteUpdate,
|
||||
}).then(response => {
|
||||
websiteId = response.body.id;
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('name', 'Cypress Website Updated');
|
||||
expect(response.body).to.have.property('domain', 'cypressupdated.com');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Updates a website with only shareId.', () => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `/api/websites/${websiteId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: { shareId: 'ABCDEF' },
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('shareId', 'ABCDEF');
|
||||
});
|
||||
});
|
||||
|
||||
it('Resets a website by removing all data related to the website.', () => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `/api/websites/${websiteId}/reset`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('ok', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('Deletes a website.', () => {
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/websites/${websiteId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('ok', true);
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.deleteTeam(teamId);
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
describe('Login tests', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/login');
|
||||
});
|
||||
|
||||
it(
|
||||
'logs user in with correct credentials and logs user out',
|
||||
{
|
||||
defaultCommandTimeout: 10000,
|
||||
},
|
||||
() => {
|
||||
cy.getDataTest('input-username').find('input').as('inputUsername').click();
|
||||
cy.get('@inputUsername').type(Cypress.env('umami_user'), { delay: 0 });
|
||||
cy.get('@inputUsername').click();
|
||||
cy.getDataTest('input-password')
|
||||
.find('input')
|
||||
.type(Cypress.env('umami_password'), { delay: 0 });
|
||||
cy.getDataTest('button-submit').click();
|
||||
cy.url().should('eq', Cypress.config().baseUrl + '/dashboard');
|
||||
cy.logout();
|
||||
},
|
||||
);
|
||||
|
||||
it('login with blank inputs or incorrect credentials', () => {
|
||||
cy.getDataTest('button-submit').click();
|
||||
cy.contains(/Required/i).should('be.visible');
|
||||
|
||||
cy.getDataTest('input-username').find('input').as('inputUsername');
|
||||
cy.get('@inputUsername').click();
|
||||
cy.get('@inputUsername').type(Cypress.env('umami_user'), { delay: 0 });
|
||||
cy.get('@inputUsername').click();
|
||||
cy.getDataTest('input-password').find('input').type('wrongpassword', { delay: 0 });
|
||||
cy.getDataTest('button-submit').click();
|
||||
cy.contains(/Incorrect username and\/or password./i).should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -1,65 +0,0 @@
|
||||
describe('User tests', () => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
||||
beforeEach(() => {
|
||||
cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
|
||||
cy.visit('/settings/users');
|
||||
});
|
||||
|
||||
it('Add a User', () => {
|
||||
// add user
|
||||
cy.contains(/Create user/i).should('be.visible');
|
||||
cy.getDataTest('button-create-user').click();
|
||||
cy.getDataTest('input-username').find('input').as('inputName').click();
|
||||
cy.get('@inputName').type('Test-user', { delay: 0 });
|
||||
cy.getDataTest('input-password').find('input').as('inputPassword').click();
|
||||
cy.get('@inputPassword').type('testPasswordCypress', { delay: 0 });
|
||||
cy.getDataTest('dropdown-role').click();
|
||||
cy.getDataTest('dropdown-item-user').click();
|
||||
cy.getDataTest('button-submit').click();
|
||||
cy.get('td[label="Username"]').should('contain.text', 'Test-user');
|
||||
cy.get('td[label="Role"]').should('contain.text', 'User');
|
||||
});
|
||||
|
||||
it('Edit a User role and password', () => {
|
||||
// edit user
|
||||
cy.get('table tbody tr')
|
||||
.contains('td', /Test-user/i)
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.getDataTest('link-button-edit').click(); // Clicks the button inside the row
|
||||
});
|
||||
cy.getDataTest('input-password').find('input').as('inputPassword').click();
|
||||
cy.get('@inputPassword').type('newPassword', { delay: 0 });
|
||||
cy.getDataTest('dropdown-role').click();
|
||||
cy.getDataTest('dropdown-item-viewOnly').click();
|
||||
cy.getDataTest('button-submit').click();
|
||||
|
||||
cy.visit('/settings/users');
|
||||
cy.get('table tbody tr')
|
||||
.contains('td', /Test-user/i)
|
||||
.parent()
|
||||
.should('contain.text', 'View only');
|
||||
|
||||
cy.logout();
|
||||
cy.url().should('eq', Cypress.config().baseUrl + '/login');
|
||||
cy.getDataTest('input-username').find('input').as('inputUsername').click();
|
||||
cy.get('@inputUsername').type('Test-user', { delay: 0 });
|
||||
cy.get('@inputUsername').click();
|
||||
cy.getDataTest('input-password').find('input').type('newPassword', { delay: 0 });
|
||||
cy.getDataTest('button-submit').click();
|
||||
cy.url().should('eq', Cypress.config().baseUrl + '/dashboard');
|
||||
});
|
||||
|
||||
it('Delete a user', () => {
|
||||
// delete user
|
||||
cy.get('table tbody tr')
|
||||
.contains('td', /Test-user/i)
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.getDataTest('button-delete').click(); // Clicks the button inside the row
|
||||
});
|
||||
cy.contains(/Are you sure you want to delete Test-user?/i).should('be.visible');
|
||||
cy.getDataTest('button-confirm').click();
|
||||
});
|
||||
});
|
||||
@@ -1,89 +0,0 @@
|
||||
describe('Website tests', () => {
|
||||
Cypress.session.clearAllSavedSessions();
|
||||
|
||||
beforeEach(() => {
|
||||
cy.login(Cypress.env('umami_user'), Cypress.env('umami_password'));
|
||||
});
|
||||
|
||||
it('Add a website', () => {
|
||||
// add website
|
||||
cy.visit('/settings/websites');
|
||||
cy.getDataTest('button-website-add').click();
|
||||
cy.contains(/Add website/i).should('be.visible');
|
||||
cy.getDataTest('input-name').find('input').as('inputUsername').click();
|
||||
cy.getDataTest('input-name').find('input').type('Add test', { delay: 0 });
|
||||
cy.getDataTest('input-domain').find('input').click();
|
||||
cy.getDataTest('input-domain').find('input').type('addtest.com', { delay: 0 });
|
||||
cy.getDataTest('button-submit').click();
|
||||
cy.get('td[label="Name"]').should('contain.text', 'Add test');
|
||||
cy.get('td[label="Domain"]').should('contain.text', 'addtest.com');
|
||||
|
||||
// clean-up data
|
||||
cy.getDataTest('link-button-edit').first().click();
|
||||
cy.contains(/Details/i).should('be.visible');
|
||||
cy.getDataTest('text-field-websiteId')
|
||||
.find('input')
|
||||
.then($input => {
|
||||
const websiteId = $input[0].value;
|
||||
cy.deleteWebsite(websiteId);
|
||||
});
|
||||
cy.visit('/settings/websites');
|
||||
cy.contains(/Add test/i).should('not.exist');
|
||||
});
|
||||
|
||||
it('Edit a website', () => {
|
||||
// prep data
|
||||
cy.addWebsite('Update test', 'updatetest.com');
|
||||
cy.visit('/settings/websites');
|
||||
|
||||
// edit website
|
||||
cy.getDataTest('link-button-edit').first().click();
|
||||
cy.contains(/Details/i).should('be.visible');
|
||||
cy.getDataTest('input-name').find('input').click();
|
||||
cy.getDataTest('input-name').find('input').clear();
|
||||
cy.getDataTest('input-name').find('input').type('Updated website', { delay: 0 });
|
||||
cy.getDataTest('input-domain').find('input').click();
|
||||
cy.getDataTest('input-domain').find('input').clear();
|
||||
cy.getDataTest('input-domain').find('input').type('updatedwebsite.com', { delay: 0 });
|
||||
cy.getDataTest('button-submit').click({ force: true });
|
||||
cy.getDataTest('input-name').find('input').should('have.value', 'Updated website');
|
||||
cy.getDataTest('input-domain').find('input').should('have.value', 'updatedwebsite.com');
|
||||
|
||||
// verify tracking script
|
||||
cy.get('div')
|
||||
.contains(/Tracking code/i)
|
||||
.click();
|
||||
cy.get('textarea').should('contain.text', Cypress.config().baseUrl + '/script.js');
|
||||
|
||||
// clean-up data
|
||||
cy.get('div')
|
||||
.contains(/Details/i)
|
||||
.click();
|
||||
cy.contains(/Details/i).should('be.visible');
|
||||
cy.getDataTest('text-field-websiteId')
|
||||
.find('input')
|
||||
.then($input => {
|
||||
const websiteId = $input[0].value;
|
||||
cy.deleteWebsite(websiteId);
|
||||
});
|
||||
cy.visit('/settings/websites');
|
||||
cy.contains(/Add test/i).should('not.exist');
|
||||
});
|
||||
|
||||
it('Delete a website', () => {
|
||||
// prep data
|
||||
cy.addWebsite('Delete test', 'deletetest.com');
|
||||
cy.visit('/settings/websites');
|
||||
|
||||
// delete website
|
||||
cy.getDataTest('link-button-edit').first().click();
|
||||
cy.contains(/Data/i).should('be.visible');
|
||||
cy.get('div').contains(/Data/i).click();
|
||||
cy.contains(/All website data will be deleted./i).should('be.visible');
|
||||
cy.getDataTest('button-delete').click();
|
||||
cy.contains(/Type DELETE in the box below to confirm./i).should('be.visible');
|
||||
cy.get('input[name="confirm"').type('DELETE');
|
||||
cy.get('button[type="submit"]').click();
|
||||
cy.contains(/Delete test/i).should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"teamCreate": {
|
||||
"name": "cypress"
|
||||
},
|
||||
"teamUpdate": {
|
||||
"name": "cypressUpdate"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"userCreate": {
|
||||
"username": "cypress1",
|
||||
"password": "password",
|
||||
"role": "user"
|
||||
},
|
||||
"userUpdate": {
|
||||
"username": "cypress1",
|
||||
"role": "view-only"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"websiteCreate": {
|
||||
"name": "Cypress Website",
|
||||
"domain": "cypress.com"
|
||||
},
|
||||
"websiteUpdate": {
|
||||
"name": "Cypress Website Updated",
|
||||
"domain": "cypressupdated.com"
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import { uuid } from '../../src/lib/crypto';
|
||||
|
||||
Cypress.Commands.add('getDataTest', (value: string) => {
|
||||
return cy.get(`[data-test=${value}]`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('logout', () => {
|
||||
cy.getDataTest('button-profile').click();
|
||||
cy.getDataTest('item-logout').click();
|
||||
cy.url().should('eq', Cypress.config().baseUrl + '/login');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('login', (username: string, password: string) => {
|
||||
cy.session([username, password], () => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/auth/login',
|
||||
body: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
Cypress.env('authorization', `bearer ${response.body.token}`);
|
||||
window.localStorage.setItem('umami.auth', JSON.stringify(response.body.token));
|
||||
})
|
||||
.its('status')
|
||||
.should('eq', 200);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('addWebsite', (name: string, domain: string) => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/websites',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: {
|
||||
id: uuid(),
|
||||
createdBy: '41e2b680-648e-4b09-bcd7-3e2b10c06264',
|
||||
name: name,
|
||||
domain: domain,
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('deleteWebsite', (websiteId: string) => {
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/websites/${websiteId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('addUser', (username: string, password: string, role: string) => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/users',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: {
|
||||
username: username,
|
||||
password: password,
|
||||
role: role,
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('deleteUser', (userId: string) => {
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/users/${userId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('addTeam', (name: string) => {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: '/api/teams',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
body: {
|
||||
name: name,
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('deleteTeam', (teamId: string) => {
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/teams/${teamId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: Cypress.env('authorization'),
|
||||
},
|
||||
}).then(response => {
|
||||
expect(response.status).to.eq(200);
|
||||
});
|
||||
});
|
||||
Vendored
-56
@@ -1,56 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
/* global JQuery */
|
||||
|
||||
declare namespace Cypress {
|
||||
interface Chainable {
|
||||
/**
|
||||
* Custom command to select DOM element by data-test attribute.
|
||||
* @example cy.getDataTest('greeting')
|
||||
*/
|
||||
getDataTest(value: string): Chainable<JQuery<HTMLElement>>;
|
||||
/**
|
||||
* Custom command to logout through UI.
|
||||
* @example cy.logout()
|
||||
*/
|
||||
logout(): Chainable<JQuery<HTMLElement>>;
|
||||
/**
|
||||
* Custom command to login user into the app.
|
||||
* @example cy.login('admin', 'password)
|
||||
*/
|
||||
login(username: string, password: string): Chainable<JQuery<HTMLElement>>;
|
||||
/**
|
||||
* Custom command to create a website
|
||||
* @example cy.addWebsite('test', 'test.com')
|
||||
*/
|
||||
addWebsite(name: string, domain: string): Chainable<JQuery<HTMLElement>>;
|
||||
/**
|
||||
* Custom command to delete a website
|
||||
* @example cy.deleteWebsite('02d89813-7a72-41e1-87f0-8d668f85008b')
|
||||
*/
|
||||
deleteWebsite(websiteId: string): Chainable<JQuery<HTMLElement>>;
|
||||
/**
|
||||
* Custom command to create a website
|
||||
* @example cy.deleteWebsite('02d89813-7a72-41e1-87f0-8d668f85008b')
|
||||
*/
|
||||
/**
|
||||
* Custom command to create a user
|
||||
* @example cy.addUser('cypress', 'password', 'User')
|
||||
*/
|
||||
addUser(username: string, password: string, role: string): Chainable<JQuery<HTMLElement>>;
|
||||
/**
|
||||
* Custom command to delete a user
|
||||
* @example cy.deleteUser('02d89813-7a72-41e1-87f0-8d668f85008b')
|
||||
*/
|
||||
deleteUser(userId: string): Chainable<JQuery<HTMLElement>>;
|
||||
/**
|
||||
* Custom command to create a team
|
||||
* @example cy.addTeam('cypressTeam')
|
||||
*/
|
||||
addTeam(name: string): Chainable<JQuery<HTMLElement>>;
|
||||
/**
|
||||
* Custom command to create a website
|
||||
* @example cy.deleteTeam('02d89813-7a72-41e1-87f0-8d668f85008b')
|
||||
*/
|
||||
deleteTeam(teamId: string): Chainable<JQuery<HTMLElement>>;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress", "node"]
|
||||
},
|
||||
"include": ["**/*.ts", "../cypress.config.ts"]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export default {
|
||||
roots: ['./src'],
|
||||
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
|
||||
transform: {
|
||||
'^.+\\.(ts|tsx)$': 'ts-jest',
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
};
|
||||
+12
-8
@@ -39,9 +39,10 @@
|
||||
"download-language-names": "node scripts/download-language-names.js",
|
||||
"change-password": "node scripts/change-password.js",
|
||||
"postbuild": "node scripts/postbuild.js",
|
||||
"test": "jest",
|
||||
"cypress-open": "cypress open cypress run",
|
||||
"cypress-run": "cypress run cypress run",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"seed-data": "tsx scripts/seed-data.ts",
|
||||
"lint": "biome lint .",
|
||||
"format": "biome format --write .",
|
||||
@@ -121,6 +122,7 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.12",
|
||||
"@netlify/plugin-nextjs": "^5.15.9",
|
||||
"@playwright/test": "^1.59.1",
|
||||
"@rollup/plugin-alias": "^6.0.0",
|
||||
"@rollup/plugin-commonjs": "^29.0.2",
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
@@ -128,15 +130,17 @@
|
||||
"@rollup/plugin-replace": "^6.0.3",
|
||||
"@rollup/plugin-terser": "^1.0.0",
|
||||
"@rollup/plugin-typescript": "^12.3.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"cypress": "^15.14.0",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"jest": "^30.3.0",
|
||||
"jsdom": "^29.1.1",
|
||||
"msw": "^2.14.5",
|
||||
"postcss": "^8.5.10",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-import": "^16.1.1",
|
||||
@@ -150,11 +154,11 @@
|
||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"tar": "^7.5.13",
|
||||
"ts-jest": "^29.4.9",
|
||||
"ts-morph": "^28.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsup": "^8.5.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^6.0.3"
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
const port = process.env.PORT ?? '3000';
|
||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://localhost:${port}`;
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests/e2e',
|
||||
fullyParallel: false,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: process.env.CI ? [['list'], ['html', { open: 'never' }]] : 'list',
|
||||
use: {
|
||||
baseURL,
|
||||
testIdAttribute: 'data-test',
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
webServer: process.env.PLAYWRIGHT_SKIP_WEB_SERVER
|
||||
? undefined
|
||||
: {
|
||||
command: process.env.PLAYWRIGHT_WEB_SERVER_COMMAND ?? 'pnpm dev',
|
||||
url: baseURL,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
});
|
||||
Generated
+1397
-2966
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
packages:
|
||||
- '**'
|
||||
ignoredBuiltDependencies:
|
||||
- cypress
|
||||
- esbuild
|
||||
- sharp
|
||||
onlyBuiltDependencies:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect, test } from '@jest/globals';
|
||||
import { expect, test } from 'vitest';
|
||||
import { getApiUrl } from '../api-url';
|
||||
|
||||
test('uses the default api path', () => {
|
||||
|
||||
@@ -257,7 +257,7 @@ export const DOMAIN_REGEX =
|
||||
/^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-_]{1,63}\.)(xn--)?[a-z0-9-_]+(-[a-z0-9-_]+)*\.)+(xn--)?[a-z0-9-_]{2,63})$/;
|
||||
export const SHARE_ID_REGEX = /^[a-zA-Z0-9]{8,50}$/;
|
||||
export const DATETIME_REGEX =
|
||||
/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{3}(Z|\+[0-9]{2}:[0-9]{2})?)?$/;
|
||||
/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{3})?(Z|\+[0-9]{2}:[0-9]{2})?$/;
|
||||
|
||||
export const URL_LENGTH = 500;
|
||||
export const PAGE_TITLE_LENGTH = 500;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { RequestHandler } from 'msw';
|
||||
|
||||
export const handlers: RequestHandler[] = [];
|
||||
@@ -0,0 +1,5 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { handlers } from './handlers';
|
||||
|
||||
export const server = setupServer(...handlers);
|
||||
export { HttpResponse, http } from 'msw';
|
||||
@@ -0,0 +1,15 @@
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
import { afterAll, afterEach, beforeAll } from 'vitest';
|
||||
import { server } from './msw/server';
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen({ onUnhandledRequest: 'error' });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type RenderOptions, render } from '@testing-library/react';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => {
|
||||
return render(ui, options);
|
||||
};
|
||||
|
||||
export * from '@testing-library/react';
|
||||
export { default as userEvent } from '@testing-library/user-event';
|
||||
export { customRender as render };
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
/// <reference types="vitest/globals" />
|
||||
/// <reference types="@testing-library/jest-dom" />
|
||||
@@ -0,0 +1,153 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { teams, users } from './fixtures';
|
||||
import { type Auth, authHeaders, deleteUser, loginViaApi, umamiUser } from './helpers';
|
||||
|
||||
test.describe('Team API tests', () => {
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
let auth: Auth;
|
||||
let teamId = '';
|
||||
let userId = '';
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
auth = await loginViaApi(request);
|
||||
|
||||
const response = await request.post('/api/users', {
|
||||
headers: authHeaders(auth),
|
||||
data: users.userCreate,
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
userId = body.id;
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('username', 'playwright1');
|
||||
expect(body).toHaveProperty('role', 'user');
|
||||
});
|
||||
|
||||
test.afterAll(async ({ request }) => {
|
||||
if (userId) {
|
||||
await deleteUser(request, auth, userId);
|
||||
}
|
||||
});
|
||||
|
||||
test('creates a team', async ({ request }) => {
|
||||
const response = await request.post('/api/teams', {
|
||||
headers: authHeaders(auth),
|
||||
data: teams.teamCreate,
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
teamId = body[0].id;
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body[0]).toHaveProperty('name', 'playwright');
|
||||
expect(body[1]).toHaveProperty('role', 'team-owner');
|
||||
});
|
||||
|
||||
test('gets a team by ID', async ({ request }) => {
|
||||
const response = await request.get(`/api/teams/${teamId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('id', teamId);
|
||||
});
|
||||
|
||||
test('updates a team', async ({ request }) => {
|
||||
const response = await request.post(`/api/teams/${teamId}`, {
|
||||
headers: authHeaders(auth),
|
||||
data: teams.teamUpdate,
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('id', teamId);
|
||||
expect(body).toHaveProperty('name', 'playwrightUpdate');
|
||||
});
|
||||
|
||||
test('gets all users that belong to a team', async ({ request }) => {
|
||||
const response = await request.get(`/api/teams/${teamId}/users`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body.data[0]).toHaveProperty('id');
|
||||
expect(body.data[0]).toHaveProperty('teamId');
|
||||
expect(body.data[0]).toHaveProperty('userId');
|
||||
expect(body.data[0]).toHaveProperty('user');
|
||||
});
|
||||
|
||||
test('gets a user belonging to a team', async ({ request }) => {
|
||||
const response = await request.get(`/api/teams/${teamId}/users/${umamiUser.id}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('teamId');
|
||||
expect(body).toHaveProperty('userId');
|
||||
expect(body).toHaveProperty('role');
|
||||
});
|
||||
|
||||
test('gets all websites belonging to a team', async ({ request }) => {
|
||||
const response = await request.get(`/api/teams/${teamId}/websites`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('data');
|
||||
});
|
||||
|
||||
test('adds a user to a team', async ({ request }) => {
|
||||
const response = await request.post(`/api/teams/${teamId}/users`, {
|
||||
headers: authHeaders(auth),
|
||||
data: {
|
||||
userId,
|
||||
role: 'team-member',
|
||||
},
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('userId', userId);
|
||||
expect(body).toHaveProperty('role', 'team-member');
|
||||
});
|
||||
|
||||
test('updates a user role on a team', async ({ request }) => {
|
||||
const response = await request.post(`/api/teams/${teamId}/users/${userId}`, {
|
||||
headers: authHeaders(auth),
|
||||
data: {
|
||||
role: 'team-view-only',
|
||||
},
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('userId', userId);
|
||||
expect(body).toHaveProperty('role', 'team-view-only');
|
||||
});
|
||||
|
||||
test('removes a user from a team', async ({ request }) => {
|
||||
const response = await request.delete(`/api/teams/${teamId}/users/${userId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
});
|
||||
|
||||
test('deletes a team', async ({ request }) => {
|
||||
const response = await request.delete(`/api/teams/${teamId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
teamId = '';
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('ok', true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { users } from './fixtures';
|
||||
import { type Auth, authHeaders, loginViaApi } from './helpers';
|
||||
|
||||
test.describe('User API tests', () => {
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
let auth: Auth;
|
||||
let userId = '';
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
auth = await loginViaApi(request);
|
||||
});
|
||||
|
||||
test('creates a user', async ({ request }) => {
|
||||
const response = await request.post('/api/users', {
|
||||
headers: authHeaders(auth),
|
||||
data: users.userCreate,
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
userId = body.id;
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('username', 'playwright1');
|
||||
expect(body).toHaveProperty('role', 'user');
|
||||
});
|
||||
|
||||
test('returns all users when admin access is used', async ({ request }) => {
|
||||
const response = await request.get('/api/admin/users', {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body.data[0]).toHaveProperty('id');
|
||||
expect(body.data[0]).toHaveProperty('username');
|
||||
expect(body.data[0]).toHaveProperty('password');
|
||||
expect(body.data[0]).toHaveProperty('role');
|
||||
});
|
||||
|
||||
test('updates a user', async ({ request }) => {
|
||||
const response = await request.post(`/api/users/${userId}`, {
|
||||
headers: authHeaders(auth),
|
||||
data: users.userUpdate,
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
userId = body.id;
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('id', userId);
|
||||
expect(body).toHaveProperty('username', 'playwright1');
|
||||
expect(body).toHaveProperty('role', 'view-only');
|
||||
});
|
||||
|
||||
test('gets a user by ID', async ({ request }) => {
|
||||
const response = await request.get(`/api/users/${userId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('id', userId);
|
||||
expect(body).toHaveProperty('username', 'playwright1');
|
||||
expect(body).toHaveProperty('role', 'view-only');
|
||||
});
|
||||
|
||||
test('deletes a user', async ({ request }) => {
|
||||
const response = await request.delete(`/api/users/${userId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('ok', true);
|
||||
});
|
||||
|
||||
test('gets all websites that belong to a user', async ({ request }) => {
|
||||
const response = await request.get(`/api/users/${userId}/websites`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('data');
|
||||
});
|
||||
|
||||
test('gets all teams that belong to a user', async ({ request }) => {
|
||||
const response = await request.get(`/api/users/${userId}/teams`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('data');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { uuid } from '../../src/lib/crypto';
|
||||
import { teams, websites } from './fixtures';
|
||||
import { type Auth, authHeaders, deleteTeam, loginViaApi } from './helpers';
|
||||
|
||||
test.describe('Website API tests', () => {
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
let auth: Auth;
|
||||
let websiteId = '';
|
||||
let teamId = '';
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
auth = await loginViaApi(request);
|
||||
|
||||
const response = await request.post('/api/teams', {
|
||||
headers: authHeaders(auth),
|
||||
data: teams.teamCreate,
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
teamId = body[0].id;
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body[0]).toHaveProperty('name', 'playwright');
|
||||
expect(body[1]).toHaveProperty('role', 'team-owner');
|
||||
});
|
||||
|
||||
test.afterAll(async ({ request }) => {
|
||||
if (teamId) {
|
||||
await deleteTeam(request, auth, teamId);
|
||||
}
|
||||
});
|
||||
|
||||
test('creates a website for user', async ({ request }) => {
|
||||
const response = await request.post('/api/websites', {
|
||||
headers: authHeaders(auth),
|
||||
data: websites.websiteCreate,
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
websiteId = body.id;
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('name', 'Playwright Website');
|
||||
expect(body).toHaveProperty('domain', 'playwright.com');
|
||||
});
|
||||
|
||||
test('creates a website for team', async ({ request }) => {
|
||||
const response = await request.post('/api/websites', {
|
||||
headers: authHeaders(auth),
|
||||
data: {
|
||||
name: 'Team Website',
|
||||
domain: 'teamwebsite.com',
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('name', 'Team Website');
|
||||
expect(body).toHaveProperty('domain', 'teamwebsite.com');
|
||||
});
|
||||
|
||||
test('creates a website with a fixed ID', async ({ request }) => {
|
||||
const fixedId = uuid();
|
||||
const response = await request.post('/api/websites', {
|
||||
headers: authHeaders(auth),
|
||||
data: { ...websites.websiteCreate, id: fixedId },
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('id', fixedId);
|
||||
expect(body).toHaveProperty('name', 'Playwright Website');
|
||||
expect(body).toHaveProperty('domain', 'playwright.com');
|
||||
|
||||
await request.delete(`/api/websites/${fixedId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
});
|
||||
|
||||
test('returns all tracked websites', async ({ request }) => {
|
||||
const response = await request.get('/api/websites', {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body.data[0]).toHaveProperty('id');
|
||||
expect(body.data[0]).toHaveProperty('name');
|
||||
expect(body.data[0]).toHaveProperty('domain');
|
||||
});
|
||||
|
||||
test('gets a website by ID', async ({ request }) => {
|
||||
const response = await request.get(`/api/websites/${websiteId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('name', 'Playwright Website');
|
||||
expect(body).toHaveProperty('domain', 'playwright.com');
|
||||
});
|
||||
|
||||
test('updates a website', async ({ request }) => {
|
||||
const response = await request.post(`/api/websites/${websiteId}`, {
|
||||
headers: authHeaders(auth),
|
||||
data: websites.websiteUpdate,
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
websiteId = body.id;
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('name', 'Playwright Website Updated');
|
||||
expect(body).toHaveProperty('domain', 'playwrightupdated.com');
|
||||
});
|
||||
|
||||
test('updates a website with only shareId', async ({ request }) => {
|
||||
const response = await request.post(`/api/websites/${websiteId}`, {
|
||||
headers: authHeaders(auth),
|
||||
data: { shareId: 'ABCDEF' },
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('shareId', 'ABCDEF');
|
||||
});
|
||||
|
||||
test('resets a website by removing all data related to the website', async ({ request }) => {
|
||||
const response = await request.post(`/api/websites/${websiteId}/reset`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('ok', true);
|
||||
});
|
||||
|
||||
test('deletes a website', async ({ request }) => {
|
||||
const response = await request.delete(`/api/websites/${websiteId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
const body = await response.json();
|
||||
|
||||
websiteId = '';
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
expect(body).toHaveProperty('ok', true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
export const users = {
|
||||
userCreate: {
|
||||
username: 'playwright1',
|
||||
password: 'password',
|
||||
role: 'user',
|
||||
},
|
||||
userUpdate: {
|
||||
username: 'playwright1',
|
||||
role: 'view-only',
|
||||
},
|
||||
};
|
||||
|
||||
export const teams = {
|
||||
teamCreate: {
|
||||
name: 'playwright',
|
||||
},
|
||||
teamUpdate: {
|
||||
name: 'playwrightUpdate',
|
||||
},
|
||||
};
|
||||
|
||||
export const websites = {
|
||||
websiteCreate: {
|
||||
name: 'Playwright Website',
|
||||
domain: 'playwright.com',
|
||||
},
|
||||
websiteUpdate: {
|
||||
name: 'Playwright Website Updated',
|
||||
domain: 'playwrightupdated.com',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
import { type APIRequestContext, expect, type Page } from '@playwright/test';
|
||||
import { uuid } from '../../src/lib/crypto';
|
||||
|
||||
export type Auth = {
|
||||
token: string;
|
||||
authorization: string;
|
||||
};
|
||||
|
||||
export const umamiUser = {
|
||||
username: process.env.UMAMI_USER ?? 'admin',
|
||||
password: process.env.UMAMI_PASSWORD ?? 'umami',
|
||||
id: process.env.UMAMI_USER_ID ?? '41e2b680-648e-4b09-bcd7-3e2b10c06264',
|
||||
};
|
||||
|
||||
export const authHeaders = (auth: Auth) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: auth.authorization,
|
||||
});
|
||||
|
||||
export async function loginViaApi(
|
||||
request: APIRequestContext,
|
||||
username = umamiUser.username,
|
||||
password = umamiUser.password,
|
||||
): Promise<Auth> {
|
||||
const response = await request.post('/api/auth/login', {
|
||||
data: { username, password },
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
const body = await response.json();
|
||||
|
||||
return {
|
||||
token: body.token,
|
||||
authorization: `bearer ${body.token}`,
|
||||
};
|
||||
}
|
||||
|
||||
export async function loginPage(page: Page, request: APIRequestContext): Promise<Auth> {
|
||||
const auth = await loginViaApi(request);
|
||||
|
||||
await page.addInitScript(token => {
|
||||
window.localStorage.setItem('umami.auth', JSON.stringify(token));
|
||||
}, auth.token);
|
||||
|
||||
return auth;
|
||||
}
|
||||
|
||||
export async function logout(page: Page) {
|
||||
await page.getByTestId('button-profile').click();
|
||||
await page.getByTestId('item-logout').click();
|
||||
await expect(page).toHaveURL(/\/login$/);
|
||||
}
|
||||
|
||||
export async function addWebsite(
|
||||
request: APIRequestContext,
|
||||
auth: Auth,
|
||||
name: string,
|
||||
domain: string,
|
||||
) {
|
||||
const response = await request.post('/api/websites', {
|
||||
headers: authHeaders(auth),
|
||||
data: {
|
||||
id: uuid(),
|
||||
createdBy: umamiUser.id,
|
||||
name,
|
||||
domain,
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
}
|
||||
|
||||
export async function deleteWebsite(request: APIRequestContext, auth: Auth, websiteId: string) {
|
||||
const response = await request.delete(`/api/websites/${websiteId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
}
|
||||
|
||||
export async function addUser(
|
||||
request: APIRequestContext,
|
||||
auth: Auth,
|
||||
username: string,
|
||||
password: string,
|
||||
role: string,
|
||||
) {
|
||||
const response = await request.post('/api/users', {
|
||||
headers: authHeaders(auth),
|
||||
data: { username, password, role },
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
}
|
||||
|
||||
export async function deleteUser(request: APIRequestContext, auth: Auth, userId: string) {
|
||||
const response = await request.delete(`/api/users/${userId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
}
|
||||
|
||||
export async function addTeam(request: APIRequestContext, auth: Auth, name: string) {
|
||||
const response = await request.post('/api/teams', {
|
||||
headers: authHeaders(auth),
|
||||
data: { name },
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
}
|
||||
|
||||
export async function deleteTeam(request: APIRequestContext, auth: Auth, teamId: string) {
|
||||
const response = await request.delete(`/api/teams/${teamId}`, {
|
||||
headers: authHeaders(auth),
|
||||
});
|
||||
|
||||
expect(response.status()).toBe(200);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { logout, umamiUser } from './helpers';
|
||||
|
||||
test.describe('Login tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
});
|
||||
|
||||
test('logs user in with correct credentials and logs user out', async ({ page }) => {
|
||||
await page.getByTestId('input-username').locator('input').fill(umamiUser.username);
|
||||
await page.getByTestId('input-password').locator('input').fill(umamiUser.password);
|
||||
await page.getByTestId('button-submit').click();
|
||||
|
||||
await expect(page).toHaveURL(/\/dashboard$/);
|
||||
|
||||
await logout(page);
|
||||
});
|
||||
|
||||
test('shows validation for blank inputs or incorrect credentials', async ({ page }) => {
|
||||
await page.getByTestId('button-submit').click();
|
||||
await expect(page.getByText(/Required/i)).toBeVisible();
|
||||
|
||||
await page.getByTestId('input-username').locator('input').fill(umamiUser.username);
|
||||
await page.getByTestId('input-password').locator('input').fill('wrongpassword');
|
||||
await page.getByTestId('button-submit').click();
|
||||
|
||||
await expect(page.getByText(/Incorrect username and\/or password./i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { loginPage, logout } from './helpers';
|
||||
|
||||
test.describe('User tests', () => {
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
test.beforeEach(async ({ page, request }) => {
|
||||
await loginPage(page, request);
|
||||
await page.goto('/settings/users');
|
||||
});
|
||||
|
||||
test('adds a user', async ({ page }) => {
|
||||
await expect(page.getByText(/Create user/i)).toBeVisible();
|
||||
|
||||
await page.getByTestId('button-create-user').click();
|
||||
await page.getByTestId('input-username').locator('input').fill('Test-user');
|
||||
await page.getByTestId('input-password').locator('input').fill('testPasswordPlaywright');
|
||||
await page.getByTestId('dropdown-role').click();
|
||||
await page.getByTestId('dropdown-item-user').click();
|
||||
await page.getByTestId('button-submit').click();
|
||||
|
||||
await expect(page.locator('td[label="Username"]')).toContainText('Test-user');
|
||||
await expect(page.locator('td[label="Role"]')).toContainText('User');
|
||||
});
|
||||
|
||||
test('edits a user role and password', async ({ page }) => {
|
||||
const userRow = page.locator('table tbody tr').filter({
|
||||
has: page.locator('td', { hasText: /Test-user/i }),
|
||||
});
|
||||
|
||||
await userRow.getByTestId('link-button-edit').click();
|
||||
await page.getByTestId('input-password').locator('input').fill('newPassword');
|
||||
await page.getByTestId('dropdown-role').click();
|
||||
await page.getByTestId('dropdown-item-viewOnly').click();
|
||||
await page.getByTestId('button-submit').click();
|
||||
|
||||
await page.goto('/settings/users');
|
||||
await expect(
|
||||
page.locator('table tbody tr').filter({ has: page.locator('td', { hasText: /Test-user/i }) }),
|
||||
).toContainText('View only');
|
||||
|
||||
await logout(page);
|
||||
await page.getByTestId('input-username').locator('input').fill('Test-user');
|
||||
await page.getByTestId('input-password').locator('input').fill('newPassword');
|
||||
await page.getByTestId('button-submit').click();
|
||||
|
||||
await expect(page).toHaveURL(/\/dashboard$/);
|
||||
});
|
||||
|
||||
test('deletes a user', async ({ page }) => {
|
||||
const userRow = page.locator('table tbody tr').filter({
|
||||
has: page.locator('td', { hasText: /Test-user/i }),
|
||||
});
|
||||
|
||||
await userRow.getByTestId('button-delete').click();
|
||||
await expect(page.getByText(/Are you sure you want to delete Test-user?/i)).toBeVisible();
|
||||
await page.getByTestId('button-confirm').click();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { addWebsite, deleteWebsite, loginPage } from './helpers';
|
||||
|
||||
test.describe('Website tests', () => {
|
||||
test('adds a website', async ({ page, request }) => {
|
||||
const auth = await loginPage(page, request);
|
||||
|
||||
await page.goto('/settings/websites');
|
||||
await page.getByTestId('button-website-add').click();
|
||||
await expect(page.getByText(/Add website/i)).toBeVisible();
|
||||
await page.getByTestId('input-name').locator('input').fill('Add test');
|
||||
await page.getByTestId('input-domain').locator('input').fill('addtest.com');
|
||||
await page.getByTestId('button-submit').click();
|
||||
|
||||
await expect(page.locator('td[label="Name"]')).toContainText('Add test');
|
||||
await expect(page.locator('td[label="Domain"]')).toContainText('addtest.com');
|
||||
|
||||
await page.getByTestId('link-button-edit').first().click();
|
||||
await expect(page.getByText(/Details/i)).toBeVisible();
|
||||
|
||||
const websiteId = await page.getByTestId('text-field-websiteId').locator('input').inputValue();
|
||||
|
||||
await deleteWebsite(request, auth, websiteId);
|
||||
await page.goto('/settings/websites');
|
||||
await expect(page.getByText(/Add test/i)).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('edits a website', async ({ page, request }) => {
|
||||
const auth = await loginPage(page, request);
|
||||
|
||||
await addWebsite(request, auth, 'Update test', 'updatetest.com');
|
||||
await page.goto('/settings/websites');
|
||||
|
||||
await page.getByTestId('link-button-edit').first().click();
|
||||
await expect(page.getByText(/Details/i)).toBeVisible();
|
||||
await page.getByTestId('input-name').locator('input').fill('Updated website');
|
||||
await page.getByTestId('input-domain').locator('input').fill('updatedwebsite.com');
|
||||
await page.getByTestId('button-submit').click();
|
||||
|
||||
await expect(page.getByTestId('input-name').locator('input')).toHaveValue('Updated website');
|
||||
await expect(page.getByTestId('input-domain').locator('input')).toHaveValue(
|
||||
'updatedwebsite.com',
|
||||
);
|
||||
|
||||
await page.getByText(/Tracking code/i).click();
|
||||
await expect(page.locator('textarea')).toContainText('/script.js');
|
||||
|
||||
await page.getByText(/Details/i).click();
|
||||
const websiteId = await page.getByTestId('text-field-websiteId').locator('input').inputValue();
|
||||
|
||||
await deleteWebsite(request, auth, websiteId);
|
||||
await page.goto('/settings/websites');
|
||||
await expect(page.getByText(/Update test/i)).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('deletes a website', async ({ page, request }) => {
|
||||
const auth = await loginPage(page, request);
|
||||
|
||||
await addWebsite(request, auth, 'Delete test', 'deletetest.com');
|
||||
await page.goto('/settings/websites');
|
||||
|
||||
await page.getByTestId('link-button-edit').first().click();
|
||||
await expect(page.getByText(/Data/i)).toBeVisible();
|
||||
await page.getByText(/Data/i).click();
|
||||
await expect(page.getByText(/All website data will be deleted./i)).toBeVisible();
|
||||
await page.getByTestId('button-delete').click();
|
||||
await expect(page.getByText(/Type DELETE in the box below to confirm./i)).toBeVisible();
|
||||
await page.locator('input[name="confirm"]').fill('DELETE');
|
||||
await page.locator('button[type="submit"]').click();
|
||||
|
||||
await expect(page.getByText(/Delete test/i)).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -39,5 +39,5 @@
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules", "./cypress.config.ts", "cypress"]
|
||||
"exclude": ["node_modules", "./playwright.config.ts", "tests/e2e"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
exclude: [
|
||||
'**/node_modules/**',
|
||||
'**/tests/e2e/**',
|
||||
'**/playwright-report/**',
|
||||
'**/test-results/**',
|
||||
],
|
||||
globals: true,
|
||||
include: ['src/**/*.{test,spec}.{ts,tsx,js,jsx}'],
|
||||
setupFiles: ['./src/test/setup.ts'],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user