mirror of
https://github.com/plankanban/planka.git
synced 2025-07-19 05:09:43 +02:00
parent
ad7fb51cfa
commit
2ee1166747
1557 changed files with 76832 additions and 47042 deletions
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
22
client/tests/acceptance/features/login.feature
Normal file
22
client/tests/acceptance/features/login.feature
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
@ -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;
|
19
client/tests/acceptance/pages/HomePage.js
Normal file
19
client/tests/acceptance/pages/HomePage.js
Normal 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);
|
||||
}
|
||||
}
|
26
client/tests/acceptance/pages/LoginPage.js
Normal file
26
client/tests/acceptance/pages/LoginPage.js
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
|
@ -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);
|
||||
});
|
60
client/tests/acceptance/steps/login.step.js
Normal file
60
client/tests/acceptance/steps/login.step.js
Normal 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}"`,
|
||||
);
|
||||
});
|
|
@ -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,
|
||||
};
|
|
@ -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."
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue