mirror of
https://github.com/plankanban/planka.git
synced 2025-07-19 05:09:43 +02:00
test: Add BDD UI tests using Playwright (#911)
This commit is contained in:
parent
4efc3be8d5
commit
096feb35bb
17 changed files with 1260 additions and 120 deletions
73
.github/workflows/build-and-test.yml
vendored
Normal file
73
.github/workflows/build-and-test.yml
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
name: Build and test
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
POSTGRES_DB: planka_db
|
||||||
|
POSTGRES_USER: user
|
||||||
|
POSTGRES_PASSWORD: password
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Setup PostgreSQL
|
||||||
|
uses: ikalnytskyi/action-setup-postgres@v5
|
||||||
|
with:
|
||||||
|
database: ${{ env.POSTGRES_DB }}
|
||||||
|
username: ${{ env.POSTGRES_USER }}
|
||||||
|
password: ${{ env.POSTGRES_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Cache Node.js modules
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: client/node_modules
|
||||||
|
key: ${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
cd client
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
- name: Setup server
|
||||||
|
env:
|
||||||
|
DEFAULT_ADMIN_EMAIL: demo@demo.demo
|
||||||
|
DEFAULT_ADMIN_PASSWORD: demo
|
||||||
|
DEFAULT_ADMIN_NAME: Demo Demo
|
||||||
|
DEFAULT_ADMIN_USERNAME: demo
|
||||||
|
run: |
|
||||||
|
client/tests/setup-symlinks.sh
|
||||||
|
cd server
|
||||||
|
cp .env.sample .env
|
||||||
|
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost/${POSTGRES_DB}|" .env
|
||||||
|
npm run db:init
|
||||||
|
npm start --prod &
|
||||||
|
|
||||||
|
- name: Wait for development server
|
||||||
|
run: |
|
||||||
|
sudo apt-get install wait-for-it -y
|
||||||
|
wait-for-it -h localhost -p 1337 -t 10
|
||||||
|
|
||||||
|
- name: Run UI tests
|
||||||
|
run: |
|
||||||
|
cd client
|
||||||
|
npm install
|
||||||
|
npx playwright install chromium
|
||||||
|
npm run test:acceptance tests
|
|
@ -1,19 +0,0 @@
|
||||||
const path = require('path');
|
|
||||||
const LAUNCH_URL = process.env.LAUNCH_URL || 'http://localhost:3000';
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
page_objects_path: path.join(__dirname, 'tests' , 'acceptance', 'pageObjects'),
|
|
||||||
test_settings: {
|
|
||||||
default: {
|
|
||||||
launch_url: LAUNCH_URL,
|
|
||||||
selenium: {
|
|
||||||
start_process: false,
|
|
||||||
host: 'localhost',
|
|
||||||
port: 4444,
|
|
||||||
},
|
|
||||||
desiredCapabilities: {
|
|
||||||
browserName: 'chrome',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
921
client/package-lock.json
generated
921
client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,7 @@
|
||||||
"lint": "eslint --ext js,jsx src config-overrides.js",
|
"lint": "eslint --ext js,jsx src config-overrides.js",
|
||||||
"start": "react-app-rewired start",
|
"start": "react-app-rewired start",
|
||||||
"test": "react-app-rewired test",
|
"test": "react-app-rewired test",
|
||||||
"test:webui": "cucumber-js --require tests/acceptance/cucumber.conf.js --require tests/acceptance/stepDefinitions -f @cucumber/pretty-formatter"
|
"test:acceptance": "cucumber-js --require tests/acceptance/cucumber.conf.js --require tests/acceptance/stepDefinitions/**/*.js --format @cucumber/pretty-formatter"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
@ -110,10 +110,12 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@cucumber/cucumber": "^7.3.1",
|
"@cucumber/cucumber": "^7.3.1",
|
||||||
"@cucumber/pretty-formatter": "^1.0.0-alpha.1",
|
"@cucumber/pretty-formatter": "^1.0.1",
|
||||||
|
"@playwright/test": "^1.46.1",
|
||||||
"@testing-library/jest-dom": "^6.5.0",
|
"@testing-library/jest-dom": "^6.5.0",
|
||||||
"@testing-library/react": "^15.0.7",
|
"@testing-library/react": "^15.0.7",
|
||||||
"@testing-library/user-event": "^14.5.2",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
|
"axios": "^1.6.2",
|
||||||
"babel-preset-airbnb": "^5.0.0",
|
"babel-preset-airbnb": "^5.0.0",
|
||||||
"chai": "^4.5.0",
|
"chai": "^4.5.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
|
@ -122,8 +124,7 @@
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.0",
|
"eslint-plugin-jsx-a11y": "^6.10.0",
|
||||||
"eslint-plugin-react": "^7.36.1",
|
"eslint-plugin-react": "^7.36.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"nightwatch": "^1.7.8",
|
"playwright": "^1.46.1",
|
||||||
"nightwatch-api": "^3.0.2",
|
|
||||||
"react-test-renderer": "18.2.0"
|
"react-test-renderer": "18.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
client/tests/acceptance/config.js
Normal file
12
client/tests/acceptance/config.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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',
|
||||||
|
};
|
|
@ -1,25 +1,35 @@
|
||||||
const {
|
// cucumber.conf.js file
|
||||||
After,
|
|
||||||
Before,
|
|
||||||
AfterAll,
|
|
||||||
BeforeAll,
|
|
||||||
setDefaultTimeout,
|
|
||||||
} = require("@cucumber/cucumber");
|
|
||||||
const { createSession, closeSession } = require("nightwatch-api");
|
|
||||||
|
|
||||||
setDefaultTimeout(60000);
|
const { Before, BeforeAll, AfterAll, After, setDefaultTimeout } = require('@cucumber/cucumber');
|
||||||
// runs before all scenarios
|
const { chromium } = require('playwright');
|
||||||
BeforeAll(async function () {});
|
const { deleteProject } = require('./testHelpers/apiHelpers');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
// runs before each scenario
|
setDefaultTimeout(config.timeout);
|
||||||
|
|
||||||
|
// launch the browser
|
||||||
|
BeforeAll(async function () {
|
||||||
|
global.browser = await chromium.launch({
|
||||||
|
// makes true for CI
|
||||||
|
headless: config.headless,
|
||||||
|
slowMo: config.slowMo,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// close the browser
|
||||||
|
AfterAll(async function () {
|
||||||
|
await global.browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a new browser context and page per scenario
|
||||||
Before(async function () {
|
Before(async function () {
|
||||||
await createSession();
|
global.context = await global.browser.newContext();
|
||||||
|
global.page = await global.context.newPage();
|
||||||
});
|
});
|
||||||
|
|
||||||
// runs after each scenario
|
// Cleanup after each scenario
|
||||||
After(async function () {
|
After(async function () {
|
||||||
await closeSession();
|
await deleteProject();
|
||||||
|
await global.page.close();
|
||||||
|
await global.context.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
// runs after all scenarios
|
|
||||||
AfterAll(async function () {});
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
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,9 +1,27 @@
|
||||||
Feature: login
|
Feature: login
|
||||||
As a user
|
As a admin
|
||||||
I want to log in
|
I want to log in
|
||||||
So that I can manage project
|
So that I can manage project
|
||||||
|
|
||||||
Scenario: User logs in with valid credentials
|
|
||||||
Given user has browsed to the login page
|
Scenario: User logs in with valid credentials
|
||||||
When user logs in with email "demo@demo.demo" and password "demo" using the webUI
|
Given user has browsed to the login page
|
||||||
Then the user should be in the dashboard 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
|
||||||
|
|
16
client/tests/acceptance/pageObjects/DashboardPage.js
Normal file
16
client/tests/acceptance/pageObjects/DashboardPage.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
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;
|
38
client/tests/acceptance/pageObjects/LoginPage.js
Normal file
38
client/tests/acceptance/pageObjects/LoginPage.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
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;
|
|
@ -1,20 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
url: function () {
|
|
||||||
return this.api.launchUrl + "/dashboard";
|
|
||||||
},
|
|
||||||
commands: {
|
|
||||||
isDashboardPage: async function () {
|
|
||||||
let result = false;
|
|
||||||
await this.waitForElementVisible("@dashboardHeader");
|
|
||||||
await this.isVisible("@dashboardHeader", (res) => {
|
|
||||||
result = res.value;
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
dashboardHeader: {
|
|
||||||
selector: "a.Header_title__3SEjb",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,26 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
url: function () {
|
|
||||||
return this.api.launchUrl + "/login";
|
|
||||||
},
|
|
||||||
commands: {
|
|
||||||
logIn: function (email, password) {
|
|
||||||
return this.waitForElementVisible("@emailInput")
|
|
||||||
.setValue("@emailInput", email)
|
|
||||||
.waitForElementVisible("@passwordInput")
|
|
||||||
.setValue("@passwordInput", password)
|
|
||||||
.waitForElementVisible("@loginBtn")
|
|
||||||
.click("@loginBtn");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
emailInput: {
|
|
||||||
selector: "input[name=emailOrUsername]",
|
|
||||||
},
|
|
||||||
passwordInput: {
|
|
||||||
selector: "input[name=password]",
|
|
||||||
},
|
|
||||||
loginBtn: {
|
|
||||||
selector: "form button",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
17
client/tests/acceptance/stepDefinitions/dashBoardContext.js
Normal file
17
client/tests/acceptance/stepDefinitions/dashBoardContext.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
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,26 +1,53 @@
|
||||||
const { Given, When, Then } = require("@cucumber/cucumber");
|
const { Given, When, Then } = require('@cucumber/cucumber');
|
||||||
const { client } = require("nightwatch-api");
|
|
||||||
const assert = require("assert");
|
|
||||||
|
|
||||||
const loginPage = client.page.loginPage();
|
// import expect for assertion
|
||||||
const dashboardPage = client.page.dashboardPage();
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
Given("user has browsed to the login page", function () {
|
// import assert
|
||||||
return loginPage.navigate();
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
When(
|
Given(
|
||||||
"user logs in with username/email {string} and password {string} using the webUI",
|
'user has logged in with email {string} and password {string}',
|
||||||
function (username, password) {
|
async function (username, password) {
|
||||||
return loginPage.logIn(username, password);
|
await loginPage.goToLoginUrl();
|
||||||
}
|
await loginPage.login(username, password);
|
||||||
|
await expect(page).toHaveURL(loginPage.homeUrl);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Then("the user should be in the dashboard page", async function () {
|
When(
|
||||||
const isDashboard = await dashboardPage.isDashboardPage();
|
'user logs in with username {string} and password {string} using the webUI',
|
||||||
assert.strictEqual(
|
async function (username, password) {
|
||||||
isDashboard,
|
await loginPage.login(username, password);
|
||||||
true,
|
},
|
||||||
"Expected to see dashboard page but not visible"
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
57
client/tests/acceptance/testHelpers/apiHelpers.js
Normal file
57
client/tests/acceptance/testHelpers/apiHelpers.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
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,
|
||||||
|
};
|
23
client/tests/setup-symlinks.sh
Executable file
23
client/tests/setup-symlinks.sh
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This script sets up symbolic links between the client build 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
|
||||||
|
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"
|
||||||
|
|
||||||
|
echo "Setup symbolic links completed successfully."
|
|
@ -32,7 +32,7 @@
|
||||||
"test": "npm run server:test && npm run client:test"
|
"test": "npm run server:test && npm run client:test"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"client/**/*.{js,jsx}": [
|
"client/src/**/*.{js,jsx}": [
|
||||||
"npm run client:lint"
|
"npm run client:lint"
|
||||||
],
|
],
|
||||||
"server/**/*.js": [
|
"server/**/*.js": [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue