plum-e2e 1.0.10 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.claude/settings.local.json +16 -1
  2. package/.vscode/settings.json +10 -0
  3. package/README.md +151 -37
  4. package/backend/_scaffold/features/LoginPage.feature +45 -3
  5. package/backend/_scaffold/pages/HomepagePage.ts +7 -0
  6. package/backend/_scaffold/pages/LoginPage.ts +37 -13
  7. package/backend/_scaffold/step_definitions/HomepageSteps.ts +6 -0
  8. package/backend/_scaffold/step_definitions/LoginSteps.ts +30 -4
  9. package/backend/_scaffold/utils/browser.ts +33 -0
  10. package/backend/_scaffold/utils/hooks.ts +8 -29
  11. package/backend/_scaffold/utils/utils.ts +3 -9
  12. package/backend/config/scripts/create-settings.js +7 -14
  13. package/backend/config/scripts/create-step.mjs +268 -0
  14. package/backend/config/scripts/generate-report.js +31 -75
  15. package/backend/config/scripts/run-tests.js +19 -4
  16. package/backend/package-lock.json +56 -641
  17. package/backend/package.json +4 -1
  18. package/backend/routes/reports.routes.js +6 -10
  19. package/backend/services/envService.js +4 -10
  20. package/backend/services/reportService.js +70 -20
  21. package/backend/services/testService.js +99 -24
  22. package/backend/tsconfig.json +2 -2
  23. package/backend/websockets/socketHandler.js +12 -6
  24. package/bin/plum.js +49 -3
  25. package/frontend/package-lock.json +436 -135
  26. package/frontend/package.json +1 -1
  27. package/frontend/src/app.css +241 -6
  28. package/frontend/src/app.html +14 -1
  29. package/frontend/src/lib/api/reports.js +68 -0
  30. package/frontend/src/lib/api/schedules.js +64 -0
  31. package/frontend/src/lib/api/tests.js +41 -0
  32. package/frontend/src/lib/components/layout/Nav.svelte +304 -0
  33. package/frontend/src/lib/components/layout/PageShell.svelte +28 -0
  34. package/frontend/src/lib/components/layout/RunnerPanel.svelte +378 -0
  35. package/frontend/src/lib/components/ui/Badge.svelte +63 -0
  36. package/frontend/src/lib/components/ui/Button.svelte +117 -0
  37. package/frontend/src/lib/components/ui/Modal.svelte +140 -0
  38. package/frontend/src/lib/components/ui/Pagination.svelte +100 -0
  39. package/frontend/src/lib/components/ui/Terminal.svelte +100 -0
  40. package/frontend/src/lib/stores/runner.js +55 -0
  41. package/frontend/src/lib/stores/theme.js +47 -0
  42. package/frontend/src/routes/+layout.svelte +7 -12
  43. package/frontend/src/routes/+page.svelte +690 -142
  44. package/frontend/src/routes/reports/+page.svelte +395 -125
  45. package/frontend/src/routes/reports/[slug]/+page.svelte +749 -0
  46. package/frontend/src/routes/scheduled-tests/+page.svelte +267 -303
  47. package/frontend/svelte.config.js +1 -4
  48. package/frontend/tailwind.config.js +2 -23
  49. package/package.json +2 -2
  50. package/backend/_scaffold/utils/world.ts +0 -25
  51. package/frontend/src/routes/components/Navigation.svelte +0 -53
@@ -6,7 +6,22 @@
6
6
  "Bash(git commit -m ':*)",
7
7
  "Bash(git push:*)",
8
8
  "Bash(gh pr:*)",
9
- "Bash(node:*)"
9
+ "Bash(node:*)",
10
+ "Bash(curl *)",
11
+ "Bash(git pull *)",
12
+ "Bash(npm install *)",
13
+ "Bash(code --list-extensions)",
14
+ "Bash(git branch *)",
15
+ "Bash(git commit -m ' *)",
16
+ "mcp__ide__getDiagnostics",
17
+ "Bash(git rm *)",
18
+ "Bash(npm test *)",
19
+ "Bash(npm run *)",
20
+ "Bash(git *)",
21
+ "Bash(python3 -c \"import sys; print\\(sys.stdin.read\\(\\)[:200]\\)\")",
22
+ "Bash(python3 -c ' *)",
23
+ "Bash(grep -rl \"^<!--$\" /Users/silverlunah/Projects/plum/frontend/src --include=\"*.svelte\")",
24
+ "Bash(python3 *)"
10
25
  ]
11
26
  }
12
27
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "cucumber.glue": [
3
+ "backend/tests/step_definitions/**/*.ts",
4
+ "backend/_scaffold/step_definitions/**/*.ts"
5
+ ],
6
+ "cucumber.features": [
7
+ "backend/tests/features/**/*.feature",
8
+ "backend/_scaffold/features/**/*.feature"
9
+ ]
10
+ }
package/README.md CHANGED
@@ -7,60 +7,174 @@
7
7
 
8
8
  ## Welcome to Plum!
9
9
 
10
- Plum makes setting up your testing framework easy. In just a few seconds, you can run the scaffold tests and write your own tests!
10
+ Plum is a ready-to-use E2E test automation environment that combines [Playwright](https://playwright.dev) and [Cucumber](https://cucumber.io). Write tests in [Gherkin](https://cucumber.io/docs/gherkin/) format, run them from the CLI or through a UI, and view reports all in one place.
11
11
 
12
- By combining [Playwright](https://playwright.dev) and [Cucumber](https://cucumber.io), tests are easy to write and read. The code follows a POM (Page Object Model) structure, making it scalable and easy for developers to understand, while Cucumber test cases are written in [Gherkin](https://cucumber.io/docs/gherkin/) format, making them accessible to non-developers as well.
13
-
14
- You can view, run, and schedule tests in a simple UI. You can even view the history of your runs in the reports page!
15
-
16
- **_Pre-requisite:_**
17
-
18
- 1. Install Docker and ensure the Docker daemon is running.
12
+ ---
19
13
 
20
14
  ## For Users
21
15
 
22
- People that want to use Plum as a test environment for their website.
16
+ > People who want to use Plum as a test environment for their website.
23
17
 
24
- **_I. How to Run:_**
18
+ ### Prerequisites
25
19
 
26
- 1. `npm install -g plum-e2e`
27
- 2. Create your project directory. Example: `mkdir my-test-folder`
28
- 3. Go inside the folder you created `cd my-test-folder`
29
- 4. Run `plum init`
30
- 1. This will initialize Plum and will create your base files:
31
- 1. `\tests` folder: This include sample test cases for [SauceLabs](https://www.saucedemo.com/v1/)
32
- 2. `.env` file: Your starting .env file. You can set the BASE_URL to your own site after you're done with the scaffold tests.
33
- 5. There are two ways to start testing:
34
- 1. By running the server. Run:<br/> `plum start` to start the server
35
- 1. Access the UI at: http://localhost:5173/
36
- 2. Without running the server. Recommended while you're writing your tests. Run:<br/> `plum dev <@test-id>`. If no test ID is included, it will run all tests
20
+ - [Node.js](https://nodejs.org)
21
+ - [Docker](https://www.docker.com) (required for `plum start`)
37
22
 
38
- ## Basic Structure
23
+ ### Setup
39
24
 
40
- After you run `plum init`, these files will be created inside your project directory.
25
+ ```bash
26
+ npm install -g plum-e2e
27
+ mkdir my-tests && cd my-tests
28
+ plum init
29
+ ```
30
+
31
+ `plum init` creates the following in your project directory and installs the [Cucumber VS Code extension](https://marketplace.visualstudio.com/items?itemName=cucumberopen.cucumber-official) for step definition linking:
41
32
 
42
33
  <pre>
43
34
  ╠═ tests
44
- ║ ╠═ features : Cucumber feature files, contains your test cases
45
- ║ ╠═ step_definitions : Reference to steps in the feature files
46
- ║ ╠═ pages : Contains functions used in step_definitions
47
- ║ ╚═ utils : Utility files (Constants, utility codes, etc.)
48
- ╚═ env. : Your .env file
35
+ ║ ╠═ features : Gherkin feature files your test cases
36
+ ║ ╠═ step_definitions : TypeScript step implementations
37
+ ║ ╠═ pages : Page Object Models
38
+ ║ ╚═ utils : Shared utilities (browser setup, constants, etc.)
39
+ ╠═ .vscode
40
+ ║ ╚═ settings.json : Cucumber extension config (step definition linking)
41
+ ╚═ .env : Environment config (BASE_URL, IS_HEADLESS)
49
42
  </pre>
50
43
 
51
- ## Tutorial
44
+ ### Commands
45
+
46
+ | Command | Description |
47
+ | ----------------------------- | -------------------------------------------------------------------------- |
48
+ | `plum init` | Scaffold the `tests/` folder and `.env` file in your project directory |
49
+ | `plum dev [tag]` | Run tests locally without Docker. Pass a tag to filter (e.g. `@test-1`) |
50
+ | `plum dev --parallel N [tag]` | Run tests in parallel with N workers (e.g. `plum dev --parallel 4`) |
51
+ | `plum start` | Start the full stack via Docker and open the UI at `http://localhost:5173` |
52
+ | `plum create-step` | Interactive prompt to generate a step definition and page object |
53
+
54
+ ---
55
+
56
+ ### Writing a Test
57
+
58
+ Tests follow a three-layer structure: **Feature → Step Definition → Page Object**.
59
+
60
+ #### 1. Feature File
61
+
62
+ Create a `.feature` file in `tests/features/`. Tags are used to filter which tests to run.
63
+
64
+ ```gherkin
65
+ # tests/features/Login.feature
66
+
67
+ @suite-login
68
+ Feature: Login
69
+
70
+ @test-login-1
71
+ Scenario: User can log in with valid credentials
72
+ Given I am on the login page
73
+ When I log in with valid credentials
74
+ Then I should see the dashboard
75
+ ```
76
+
77
+ #### 2. Page Object Model
78
+
79
+ Create a `.ts` file in `tests/pages/`. Methods are **static** — they use `page()` internally so you never need to pass a page instance around.
80
+
81
+ ```typescript
82
+ // tests/pages/LoginPage.ts
83
+
84
+ import { page } from '../utils/browser';
85
+
86
+ export class LoginPage {
87
+ static async goToLoginPage() {
88
+ await page().goto(process.env.BASE_URL as string);
89
+ }
90
+
91
+ static async login(email: string, password: string) {
92
+ await page().fill('#email', email);
93
+ await page().fill('#password', password);
94
+ await page().click('button[type="submit"]');
95
+ }
96
+
97
+ static async verifyDashboardVisible() {
98
+ await page().waitForSelector('#dashboard');
99
+ }
100
+ }
101
+ ```
102
+
103
+ #### 3. Step Definition
104
+
105
+ Create a `.ts` file in `tests/step_definitions/`. Just import the page and call its static methods directly.
106
+
107
+ ```typescript
108
+ // tests/step_definitions/LoginSteps.ts
109
+
110
+ import { Given, When, Then } from '@cucumber/cucumber';
111
+ import { LoginPage } from '../pages/LoginPage';
112
+
113
+ Given('I am on the login page', async () => {
114
+ await LoginPage.goToLoginPage();
115
+ });
116
+
117
+ When('I log in with valid credentials', async () => {
118
+ await LoginPage.login('user@example.com', 'password');
119
+ });
120
+
121
+ Then('I should see the dashboard', async () => {
122
+ await LoginPage.verifyDashboardVisible();
123
+ });
124
+ ```
125
+
126
+ #### Running your test
127
+
128
+ ```bash
129
+ plum dev @test-login-1 # run a single scenario
130
+ plum dev @suite-login # run the whole suite
131
+ plum dev # run all tests
132
+ ```
133
+
134
+ ---
135
+
136
+ ## For Developers
137
+
138
+ > People who want to contribute to or develop Plum itself.
139
+
140
+ ### Setup
141
+
142
+ ```bash
143
+ git clone https://github.com/silverlunah/plum.git
144
+ cd plum
145
+ npm run init
146
+ ```
147
+
148
+ `npm run init` installs all dependencies across the root, backend, and frontend.
149
+
150
+ ### Backend Local Commands
151
+
152
+ Run these from the `backend/` directory:
153
+
154
+ | Command | Description |
155
+ | -------------------------- | ---------------------------------------------------------------- |
156
+ | `npm run init` | Create `.env` and copy scaffold into `backend/tests/` |
157
+ | `npm test [-- tag]` | Run tests from `backend/tests/` directly |
158
+ | `npm test -- --parallel N` | Run tests in parallel with N workers |
159
+ | `npm run create-step` | Interactive prompt to generate a step definition and page object |
52
160
 
53
- 1. For a complete guide on how to write tests, visit our [Coming Soon](https://github.com/silverlunah/plum/wiki)
54
- 2. An easy way to learn is to check the scaffold files starting from the Feature files -> Step Definitions -> Page files and utils/world.ts for the CustomWorld initialization. Those are the main files you need to write a test case.
161
+ ```bash
162
+ cd backend
163
+ npm run init # sets up .env and tests/ for local development
164
+ npm test # run all tests
165
+ npm test -- @test-1 # run a specific test by tag
166
+ npm test -- --parallel 4 # run tests in parallel
167
+ npm run create-step # generate a new step and page interactively
168
+ ```
55
169
 
56
- ## For Developers/Contributors
170
+ ### Docker
57
171
 
58
- For people that want to contribute to the project
172
+ ```bash
173
+ npm run docker:up # build and start all services
174
+ npm run docker:down # stop all services
175
+ ```
59
176
 
60
- 1. Clone the project `git clone https://github.com/silverlunah/plum.git`
61
- 2. `cd plum`
62
- 3. Initialize the project by:<br/>`npm run init`
63
- 4. Check if its running:<br/> `docker compose up --build -d`
177
+ ---
64
178
 
65
179
  ## Other
66
180
 
@@ -1,6 +1,48 @@
1
- @suite-1
1
+ @suite-login
2
2
  Feature: Demo Sauce Login
3
3
 
4
+ # Background runs before every scenario in this file
5
+ Background:
6
+ Given I am in Demo Sauce Login page
7
+
8
+ # ── Basic Scenario ─────────────────────────────────────────────────────────
4
9
  @test-1
5
- Scenario: Verify User Can Login Using Correct Details
6
- Given I am in Demo Sauce Login page
10
+ Scenario: User can log in with valid credentials
11
+ When I enter "standard_user" in username field
12
+ And I enter "secret_sauce" in password field
13
+ And I click on the login button
14
+ Then I should be navigated to the products page
15
+
16
+ @test-2
17
+ Scenario: User cannot log in with invalid credentials
18
+ When I enter "invalid_user" in username field
19
+ And I enter "invalid_password" in password field
20
+ And I click on the login button
21
+ Then the login should fail
22
+
23
+ # ── Scenario Outline ───────────────────────────────────────────────────────
24
+ # Runs once per row in the Examples table.
25
+ # Use <placeholder> in steps to reference column headers.
26
+ @test-3
27
+ Scenario Outline: User login attempts with different credentials
28
+ When I enter "<username>" in username field
29
+ And I enter "<password>" in password field
30
+ And I click on the login button
31
+ Then the login outcome should be "<outcome>"
32
+
33
+ Examples:
34
+ | username | password | outcome |
35
+ | standard_user | secret_sauce | success |
36
+ | locked_out_user | secret_sauce | failure |
37
+ | invalid_user | wrong_pass | failure |
38
+
39
+ # ── Data Table ─────────────────────────────────────────────────────────────
40
+ # Pass structured data directly into a step.
41
+ # Access rows in your step definition via dataTable.hashes().
42
+ @test-4
43
+ Scenario: User can log in using a data table
44
+ When I fill in the login form:
45
+ | field | value |
46
+ | username | standard_user |
47
+ | password | secret_sauce |
48
+ Then I should be navigated to the products page
@@ -0,0 +1,7 @@
1
+ import { page } from '../utils/browser';
2
+
3
+ export class HomepagePage {
4
+ static async iShouldBeNavigatedToTheProductsPage() {
5
+ await page().waitForSelector('.title');
6
+ }
7
+ }
@@ -1,24 +1,48 @@
1
- // loginPage.ts
2
- import test, { Page } from '@playwright/test';
1
+ import test from '@playwright/test';
2
+ import { page } from '../utils/browser';
3
3
  import { SAMPLE_CONSTANT } from '../utils/constants';
4
4
  import { Utils } from '../utils/utils';
5
5
 
6
6
  export class LoginPage {
7
- private page: Page;
8
- private utils: Utils;
7
+ static async goToLoginPage() {
8
+ console.log(SAMPLE_CONSTANT);
9
+ await Utils.goToPage(process.env.BASE_URL as string);
10
+ await page().waitForTimeout(3000);
11
+ }
9
12
 
10
- constructor(page: Page) {
11
- this.page = page;
12
- this.utils = new Utils(this.page);
13
+ static async skipTest() {
14
+ test.skip();
13
15
  }
14
16
 
15
- async goToLoginPage() {
16
- console.log(SAMPLE_CONSTANT);
17
- await this.utils.goToPage(process.env.BASE_URL as string);
18
- await this.page.waitForTimeout(3000);
17
+ static async iEnterUsername(username: string) {
18
+ await page().fill('#user-name', username);
19
19
  }
20
20
 
21
- async skipTest() {
22
- test.skip();
21
+ static async iEnterPassword(password: string) {
22
+ await page().fill('#password', password);
23
+ }
24
+
25
+ static async iClickOnTheLoginButton() {
26
+ await page().click('#login-button');
27
+ }
28
+
29
+ static async fillLoginForm(fields: { field: string; value: string }[]) {
30
+ for (const { field, value } of fields) {
31
+ if (field === 'username') await page().fill('#user-name', value);
32
+ if (field === 'password') await page().fill('#password', value);
33
+ }
34
+ await page().click('#login-button');
35
+ }
36
+
37
+ static async verifyLoginOutcome(outcome: string) {
38
+ if (outcome === 'success') {
39
+ await page().waitForSelector('.title');
40
+ } else {
41
+ await page().waitForSelector('.error-message-container');
42
+ }
43
+ }
44
+
45
+ static async verifyLoginFailed() {
46
+ await page().waitForSelector('.error-message-container');
23
47
  }
24
48
  }
@@ -0,0 +1,6 @@
1
+ import { Then } from '@cucumber/cucumber';
2
+ import { HomepagePage } from '../pages/HomepagePage';
3
+
4
+ Then('I should be navigated to the products page', async () => {
5
+ await HomepagePage.iShouldBeNavigatedToTheProductsPage();
6
+ });
@@ -1,9 +1,35 @@
1
- import { Given } from '@cucumber/cucumber';
1
+ import { Given, When, Then, DataTable } from '@cucumber/cucumber';
2
+ import { LoginPage } from '../pages/LoginPage';
2
3
 
3
- Given('I am in Demo Sauce Login page', async function () {
4
- await this.loginPage.goToLoginPage();
4
+ Given('I am in Demo Sauce Login page', async () => {
5
+ await LoginPage.goToLoginPage();
5
6
  });
6
7
 
7
- Given('I fail this test', async function () {
8
+ Given('I fail this test', async () => {
8
9
  throw new Error('err');
9
10
  });
11
+
12
+ When('I enter {string} in username field', async (username: string) => {
13
+ await LoginPage.iEnterUsername(username);
14
+ });
15
+
16
+ When('I enter {string} in password field', async (password: string) => {
17
+ await LoginPage.iEnterPassword(password);
18
+ });
19
+
20
+ When('I click on the login button', async () => {
21
+ await LoginPage.iClickOnTheLoginButton();
22
+ });
23
+
24
+ When('I fill in the login form:', async (dataTable: DataTable) => {
25
+ const fields = dataTable.hashes() as { field: string; value: string }[];
26
+ await LoginPage.fillLoginForm(fields);
27
+ });
28
+
29
+ Then('the login outcome should be {string}', async (outcome: string) => {
30
+ await LoginPage.verifyLoginOutcome(outcome);
31
+ });
32
+
33
+ Then('the login should fail', async () => {
34
+ await LoginPage.verifyLoginFailed();
35
+ });
@@ -0,0 +1,33 @@
1
+ import { chromium, Browser, BrowserContext, Page } from 'playwright';
2
+
3
+ let _browser: Browser;
4
+ let _context: BrowserContext;
5
+ let _page: Page;
6
+
7
+ export const page = (): Page => _page;
8
+
9
+ export async function setup(): Promise<void> {
10
+ const isHeadless = process.env.IS_HEADLESS?.toLowerCase() !== 'false';
11
+ _browser = await chromium.launch({ headless: isHeadless });
12
+ _context = await _browser.newContext();
13
+ _page = await _context.newPage();
14
+ }
15
+
16
+ export async function teardown(
17
+ attach: (data: Buffer, mime: string) => Promise<void>,
18
+ failed: boolean
19
+ ): Promise<void> {
20
+ if (failed && _page) {
21
+ const screenshotDir = 'reports/screenshots';
22
+ const fs = await import('fs');
23
+ const path = await import('path');
24
+ if (!fs.existsSync(screenshotDir)) {
25
+ fs.mkdirSync(screenshotDir, { recursive: true });
26
+ }
27
+ const screenshotPath = path.join(screenshotDir, `screenshot_${Date.now()}.png`);
28
+ await _page.screenshot({ path: screenshotPath });
29
+ const screenshotData = fs.readFileSync(screenshotPath);
30
+ await attach(screenshotData, 'image/png');
31
+ }
32
+ await _browser?.close();
33
+ }
@@ -1,36 +1,15 @@
1
- import { Before, After, setWorldConstructor, ITestCaseHookParameter } from '@cucumber/cucumber';
2
- import { chromium } from 'playwright';
3
- import fs from 'fs';
4
- import path from 'path';
5
- import { CustomWorld } from './world';
1
+ import { Before, After, ITestCaseHookParameter } from '@cucumber/cucumber';
2
+ import { setup, teardown } from './browser';
6
3
  import dotenv from 'dotenv';
7
4
 
8
5
  dotenv.config();
9
- setWorldConstructor(CustomWorld);
10
6
 
11
- Before(async function (this: CustomWorld) {
12
- const isHeadless = process.env.IS_HEADLESS?.toLowerCase() !== 'false';
13
-
14
- this.browser = await chromium.launch({ headless: isHeadless });
15
- this.context = await this.browser.newContext();
16
- this.page = await this.context.newPage();
17
-
18
- this.initPages(this.page);
7
+ Before(async ({ pickle }: ITestCaseHookParameter) => {
8
+ const tags = pickle.tags.map((t) => t.name).join(' ');
9
+ console.log(`\n▶ ${pickle.name}${tags ? ` ${tags}` : ''}`);
10
+ await setup();
19
11
  });
20
12
 
21
- After(async function (this: CustomWorld, scenario: ITestCaseHookParameter) {
22
- if (scenario.result?.status === 'FAILED' && this.page) {
23
- const screenshotDir = path.join('reports', 'screenshots');
24
- if (!fs.existsSync(screenshotDir)) {
25
- fs.mkdirSync(screenshotDir, { recursive: true });
26
- }
27
-
28
- const screenshotPath = path.join(screenshotDir, `screenshot_${Date.now()}.png`);
29
- await this.page.screenshot({ path: screenshotPath });
30
-
31
- const screenshotData = fs.readFileSync(screenshotPath);
32
- await this.attach(screenshotData, 'image/png');
33
- }
34
-
35
- await this.browser?.close();
13
+ After(async function (scenario: ITestCaseHookParameter) {
14
+ await teardown(this.attach.bind(this), scenario.result?.status === 'FAILED');
36
15
  });
@@ -1,13 +1,7 @@
1
- import { Page } from '@playwright/test';
1
+ import { page } from './browser';
2
2
 
3
3
  export class Utils {
4
- private page: Page;
5
-
6
- constructor(page: Page) {
7
- this.page = page;
8
- }
9
-
10
- async goToPage(url: string) {
11
- await this.page.goto(url);
4
+ static async goToPage(url: string) {
5
+ await page().goto(url);
12
6
  }
13
7
  }
@@ -17,18 +17,14 @@
17
17
 
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
- const fse = require('fs-extra'); // fs-extra for directory copying
20
+ const fse = require('fs-extra');
21
+ const pc = require('picocolors');
21
22
 
22
- // Path to the settings.json file inside the backend/config directory
23
23
  const settingsFilePath = path.join(process.cwd(), 'config', 'settings.json');
24
24
  const scaffoldFolderPath = path.join(process.cwd(), '_scaffold');
25
25
  const userTestsFolderPath = path.join(process.cwd(), 'tests');
26
26
 
27
- // Check if the settings.json file exists
28
27
  if (!fs.existsSync(settingsFilePath)) {
29
- console.log('⚠️ settings.json not found. Creating it with default values...');
30
-
31
- // Default content for settings.json
32
28
  const settingsContent = JSON.stringify(
33
29
  {
34
30
  reportsHistory: 20,
@@ -42,19 +38,16 @@ if (!fs.existsSync(settingsFilePath)) {
42
38
  null,
43
39
  2
44
40
  );
45
-
46
- // Write the settings to the settings.json file
47
41
  fs.writeFileSync(settingsFilePath, settingsContent, 'utf8');
48
- console.log(' settings.json created with default values.');
42
+ console.log(pc.green('✓') + ' settings.json created with default values.');
49
43
  } else {
50
- console.log('⚠️ settings.json already exists. Skipping creation.');
44
+ console.log(pc.yellow('⚠') + ' settings.json already exists. Skipping creation.');
51
45
  }
52
46
 
53
- // Check if the tests folder exists, if not, copy the scaffold folder as tests
54
47
  if (!fs.existsSync(userTestsFolderPath)) {
55
- console.log('🧪 `tests/` folder not found. Creating `tests/` from the scaffold folder...');
48
+ console.log(pc.cyan('↳') + ' tests/ not found. Creating from scaffold...');
56
49
  fse.copySync(scaffoldFolderPath, userTestsFolderPath);
57
- console.log(' `tests/` folder created from scaffold.');
50
+ console.log(pc.green('✓') + ' tests/ created from scaffold.');
58
51
  } else {
59
- console.log('⚠️ `tests/` folder already exists. Skipping creation.');
52
+ console.log(pc.yellow('⚠') + ' tests/ already exists. Skipping creation.');
60
53
  }