1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-19 05:09:43 +02:00

feat: Version 2

Closes #627, closes #1047
This commit is contained in:
Maksim Eltyshev 2025-05-10 02:09:06 +02:00
parent ad7fb51cfa
commit 2ee1166747
1557 changed files with 76832 additions and 47042 deletions

View file

@ -1,12 +1,14 @@
module.exports = {
// environment
adminUser: {
email: 'demo@demo.demo',
password: 'demo',
},
baseUrl: process.env.BASE_URL ?? 'http://localhost:1337/',
// playwright
slowMo: parseInt(process.env.SLOW_MO, 10) || 1000,
timeout: parseInt(process.env.TIMEOUT, 10) || 6000,
headless: process.env.HEADLESS !== 'true',
const BASE_URL = process.env.BASE_URL || 'http://localhost:1337';
const TIMEOUT = parseInt(process.env.TIMEOUT, 10) || 6000;
const PLAYWRIGHT = {
headless: process.env.PLAYWRIGHT_HEADLESS !== 'false',
slowMo: parseInt(process.env.PLAYWRIGHT_SLOW_MO, 10) || 1000,
};
export default {
BASE_URL,
TIMEOUT,
PLAYWRIGHT,
};

View file

@ -1,35 +1,24 @@
// cucumber.conf.js file
import { After, AfterAll, Before, BeforeAll, setDefaultTimeout } from '@cucumber/cucumber';
import { chromium } from 'playwright';
const { Before, BeforeAll, AfterAll, After, setDefaultTimeout } = require('@cucumber/cucumber');
const { chromium } = require('playwright');
const { deleteProject } = require('./testHelpers/apiHelpers');
const config = require('./config');
import Config from './Config.js';
setDefaultTimeout(config.timeout);
setDefaultTimeout(Config.TIMEOUT);
// launch the browser
BeforeAll(async function () {
global.browser = await chromium.launch({
// makes true for CI
headless: config.headless,
slowMo: config.slowMo,
});
BeforeAll(async () => {
global.browser = await chromium.launch(Config.PLAYWRIGHT);
});
// close the browser
AfterAll(async function () {
await global.browser.close();
});
// Create a new browser context and page per scenario
Before(async function () {
Before(async () => {
global.context = await global.browser.newContext();
global.page = await global.context.newPage();
});
// Cleanup after each scenario
After(async function () {
await deleteProject();
After(async () => {
await global.page.close();
await global.context.close();
});
AfterAll(async () => {
await global.browser.close();
});

View file

@ -0,0 +1,22 @@
Feature: Login
Background:
Given the user navigates to the login page
Scenario: User logs in with valid credentials
When the user logs in with email or username "demo" and password "demo" via the web UI
Then the user should be redirected to the home page
Scenario Outline: User logs in with invalid credentials
When the user logs in with email or username "<emailOrUsername>" and password "<password>" via the web UI
Then the user should see the message "<message>"
Examples:
| emailOrUsername | password | message |
| spiderman | spider123 | Invalid credentials |
| ironman | iron123 | Invalid credentials |
| aquaman | aqua123 | Invalid credentials |
Scenario: User logs out
Given the user is logged in with email or username "demo" and password "demo"
When the user logs out via the web UI
Then the user should be redirected to the login page

View file

@ -1,10 +0,0 @@
Feature: dashboard
As a admin
I want to create a project
So that I can manage project
Scenario: create a new project
Given user has browsed to the login page
And user has logged in with email "demo@demo.demo" and password "demo"
When the user creates a project with name "testproject" using the webUI
Then the created project "testproject" should be opened

View file

@ -1,27 +0,0 @@
Feature: login
As a admin
I want to log in
So that I can manage project
Scenario: User logs in with valid credentials
Given user has browsed to the login page
When user logs in with username "demo@demo.demo" and password "demo" using the webUI
Then the user should be in dashboard page
Scenario Outline: login with invalid username and invalid password
Given user has browsed to the login page
When user logs in with username "<username>" and password "<password>" using the webUI
Then user should see the error message "<message>"
Examples:
| username | password | message |
| spiderman | spidy123 | Invalid credentials |
| ironman | iron123 | Invalid credentials |
| aquaman | aqua123 | Invalid credentials |
Scenario: User can log out
Given user has logged in with email "demo@demo.demo" and password "demo"
When user logs out using the webUI
Then the user should be in the login page

View file

@ -1,16 +0,0 @@
class DashboardPage {
constructor() {
this.createProjectIconSelector = `.Projects_addTitle__tXhB4`;
this.projectTitleInputSelector = `input[name="name"]`;
this.createProjectButtonSelector = `//button[text()="Create project"]`;
this.projectTitleSelector = `//div[@class="item Header_item__OOEY7 Header_title__l+wMf"][text()="%s"]`;
}
async createProject(project) {
await page.click(this.createProjectIconSelector);
await page.fill(this.projectTitleInputSelector, project);
await page.click(this.createProjectButtonSelector);
}
}
module.exports = DashboardPage;

View file

@ -1,38 +0,0 @@
const config = require(`../config`);
class LoginPage {
constructor() {
// url
this.homeUrl = config.baseUrl;
this.loginUrl = `${this.homeUrl}login`;
// selectors
this.loginButtonSelector = `//i[@class="right arrow icon"]`;
this.usernameSelector = `//input[@name='emailOrUsername']`;
this.passwordSelector = `//input[@name='password']`;
this.errorMessageSelector = `//div[@class='ui error visible message']`;
this.userActionSelector = `//span[@class="User_initials__9Wp90"]`;
this.logOutSelector = `//a[@class="item UserStep_menuItem__5pvtT"][contains(text(),'Log Out')]`;
}
async goToLoginUrl() {
await page.goto(this.loginUrl);
}
async logOut() {
await page.click(this.userActionSelector);
await page.click(this.logOutSelector);
}
async login(username, password) {
await page.fill(this.usernameSelector, username);
await page.fill(this.passwordSelector, password);
await page.click(this.loginButtonSelector);
}
async getErrorMessage() {
return page.innerText(this.errorMessageSelector);
}
}
module.exports = LoginPage;

View file

@ -0,0 +1,19 @@
import Config from '../Config.js';
export default class HomePage {
constructor() {
this.url = Config.BASE_URL;
this.userActionSelector = 'div.menu > a:last-child';
this.logOutSelector = 'a:has-text("Log Out")';
}
async navigate() {
await page.goto(this.url);
}
async logout() {
await page.click(this.userActionSelector);
await page.click(this.logOutSelector);
}
}

View file

@ -0,0 +1,26 @@
import Config from '../Config.js';
export default class LoginPage {
constructor() {
this.url = `${Config.BASE_URL}/login`;
this.emailOrUsernameInputSelector = 'input[name="emailOrUsername"]';
this.passwordInputSelector = 'input[name="password"]';
this.logInButtonSelector = 'button.primary';
this.messageSelector = 'div.message > div.content > p';
}
async navigate() {
await page.goto(this.url);
}
async login(emailOrUsername, password) {
await page.fill(this.emailOrUsernameInputSelector, emailOrUsername);
await page.fill(this.passwordInputSelector, password);
await page.click(this.logInButtonSelector);
}
async getMessage() {
return page.innerText(this.messageSelector);
}
}

View file

@ -1,17 +0,0 @@
const { When, Then } = require('@cucumber/cucumber');
const util = require('util');
const { expect } = require('playwright/test');
const DashboardPage = require('../pageObjects/DashboardPage');
const dashboardPage = new DashboardPage();
When('the user creates a project with name {string} using the webUI', async function (project) {
await dashboardPage.createProject(project);
});
Then('the created project {string} should be opened', async function (project) {
expect(
await page.locator(util.format(dashboardPage.projectTitleSelector, project)),
).toBeVisible();
});

View file

@ -1,53 +0,0 @@
const { Given, When, Then } = require('@cucumber/cucumber');
// import expect for assertion
const { expect } = require('@playwright/test');
// import assert
const assert = require('assert');
const LoginPage = require('../pageObjects/LoginPage');
const loginPage = new LoginPage();
Given('user has browsed to the login page', async function () {
await loginPage.goToLoginUrl();
await expect(page).toHaveURL(loginPage.loginUrl);
});
Given(
'user has logged in with email {string} and password {string}',
async function (username, password) {
await loginPage.goToLoginUrl();
await loginPage.login(username, password);
await expect(page).toHaveURL(loginPage.homeUrl);
},
);
When(
'user logs in with username {string} and password {string} using the webUI',
async function (username, password) {
await loginPage.login(username, password);
},
);
Then('the user should be in dashboard page', async function () {
await expect(page).toHaveURL(loginPage.homeUrl);
});
Then('user should see the error message {string}', async function (errorMessage) {
const actualErrorMessage = await loginPage.getErrorMessage();
assert.equal(
actualErrorMessage,
errorMessage,
`Expected message to be "${errorMessage}" but receive "${actualErrorMessage}"`,
);
});
When('user logs out using the webUI', async function () {
await loginPage.logOut();
});
Then('the user should be in the login page', async function () {
await expect(page).toHaveURL(loginPage.loginUrl);
});

View file

@ -0,0 +1,60 @@
import assert from 'assert';
import { Given, Then, When } from '@cucumber/cucumber';
import { expect } from '@playwright/test';
import LoginPage from '../pages/LoginPage.js';
import HomePage from '../pages/HomePage.js';
const loginPage = new LoginPage();
const homePage = new HomePage();
// ---------- GIVEN ----------
Given('the user navigates to the login page', async () => {
await loginPage.navigate();
await expect(page).toHaveURL(loginPage.url);
});
Given(
'the user is logged in with email or username {string} and password {string}',
async (emailOrUsername, password) => {
await loginPage.navigate();
await loginPage.login(emailOrUsername, password);
await expect(page).toHaveURL(homePage.url);
},
);
// ---------- WHEN ----------
When(
'the user logs in with email or username {string} and password {string} via the web UI',
async (emailOrUsername, password) => {
await loginPage.login(emailOrUsername, password);
},
);
When('the user logs out via the web UI', async () => {
await homePage.logout();
});
// ---------- THEN ----------
Then('the user should be redirected to the home page', async () => {
await expect(page).toHaveURL(homePage.url);
});
Then('the user should be redirected to the login page', async () => {
await expect(page).toHaveURL(loginPage.url);
});
Then('the user should see the message {string}', async (expectedMessage) => {
const message = await loginPage.getMessage();
assert.strictEqual(
message,
expectedMessage,
`Expected message to be "${expectedMessage}", but received "${message}"`,
);
});

View file

@ -1,57 +0,0 @@
const axios = require('axios');
const config = require('../config');
async function getXauthToken() {
try {
const res = await axios.post(
`${config.baseUrl}api/access-tokens`,
{
emailOrUsername: config.adminUser.email,
password: config.adminUser.password,
},
{
headers: {
'Content-Type': 'application/json',
},
},
);
return res.data.item;
} catch (error) {
return `Error requesting access token: ${error.message}`;
}
}
async function getProjectIDs() {
try {
const res = await axios.get(`${config.baseUrl}api/projects`, {
headers: {
Authorization: `Bearer ${await getXauthToken()}`,
},
});
return res.data.items.map((project) => project.id);
} catch (error) {
return `Error requesting projectIDs: ${error.message}`;
}
}
async function deleteProject() {
try {
const projectIDs = await getProjectIDs();
await Promise.all(
projectIDs.map(async (project) => {
await axios.delete(`${config.baseUrl}api/projects/${project}`, {
headers: {
Authorization: `Bearer ${await getXauthToken()}`,
},
});
}),
);
return true;
} catch (error) {
return `Error deleting project: ${error.message}`;
}
}
module.exports = {
deleteProject,
};

View file

@ -1,23 +1,22 @@
#!/bin/bash
# This script sets up symbolic links between the client build files and the server directories,
# This script sets up symbolic links between the client dist files and the server directories
# Navigate to the root directory of the git repository
cd "$(git rev-parse --show-toplevel)" || { echo "Failed to navigate to the git repository root"; exit 1; }
# Store paths for the client build, server public directory, and server views directory
CLIENT_PATH=$(pwd)/client/build
# Store paths for the client dist, server public, and server views
CLIENT_PATH=$(pwd)/client/dist
SERVER_PUBLIC_PATH=$(pwd)/server/public
SERVER_VIEWS_PATH=$(pwd)/server/views
# Create symbolic links for the necessary client assets in the server's public and views directories
ln -s ${CLIENT_PATH}/asset-manifest.json ${SERVER_PUBLIC_PATH}/asset-manifest.json && echo "Linked asset-manifest.json successfully"
ln -s ${CLIENT_PATH}/favicon.ico ${SERVER_PUBLIC_PATH}/favicon.ico && echo "Linked favicon.ico successfully"
ln -s ${CLIENT_PATH}/logo192.png ${SERVER_PUBLIC_PATH}/logo192.png && echo "Linked logo192.png successfully"
ln -s ${CLIENT_PATH}/logo512.png ${SERVER_PUBLIC_PATH}/logo512.png && echo "Linked logo512.png successfully"
ln -s ${CLIENT_PATH}/manifest.json ${SERVER_PUBLIC_PATH}/manifest.json && echo "Linked manifest.json successfully"
ln -s ${CLIENT_PATH}/robots.txt ${SERVER_PUBLIC_PATH}/robots.txt && echo "Linked robots.txt successfully"
ln -s ${CLIENT_PATH}/static ${SERVER_PUBLIC_PATH}/static && echo "Linked static folder successfully"
ln -s ${CLIENT_PATH}/index.html ${SERVER_VIEWS_PATH}/index.ejs && echo "Linked index.html to index.ejs successfully"
ln -s ${CLIENT_PATH}/assets ${SERVER_PUBLIC_PATH}/assets && echo "Linked assets folder successfully"
ln -s ${CLIENT_PATH}/index.html ${SERVER_VIEWS_PATH}/index.html && echo "Linked index.html successfully"
echo "Setup symbolic links completed successfully."