@fishawack/lab-env 4.43.0 → 4.44.0
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.
- package/CHANGELOG.md +14 -0
- package/_Ai/webdriveriov9-capture.md +232 -0
- package/globals.js +13 -10
- package/laravel/10/docker-compose.yml +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### 4.44.0 (2025-08-17)
|
|
4
|
+
|
|
5
|
+
#### Features
|
|
6
|
+
|
|
7
|
+
* added capture webdriverio ai file ([66ef769](https://bitbucket.org/fishawackdigital/lab-env/commits/66ef7691dd48ba209d1425a27b03cd1fe20205a6))
|
|
8
|
+
* enable ssh cloning via php container ([cb47b41](https://bitbucket.org/fishawackdigital/lab-env/commits/cb47b417a832d998d82f4c1e1faef0c2ab33f67f))
|
|
9
|
+
|
|
10
|
+
### 4.44.0-beta.1 (2025-08-15)
|
|
11
|
+
|
|
12
|
+
#### Features
|
|
13
|
+
|
|
14
|
+
* added capture webdriverio ai file ([66ef769](https://bitbucket.org/fishawackdigital/lab-env/commits/66ef7691dd48ba209d1425a27b03cd1fe20205a6))
|
|
15
|
+
* enable ssh cloning via php container ([cb47b41](https://bitbucket.org/fishawackdigital/lab-env/commits/cb47b417a832d998d82f4c1e1faef0c2ab33f67f))
|
|
16
|
+
|
|
3
17
|
### 4.43.0 (2025-08-10)
|
|
4
18
|
|
|
5
19
|
#### Features
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "_Node/capture.js,_Node/capture-*.js,_Node/capture/**/*.js"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Project Overview
|
|
6
|
+
|
|
7
|
+
This project uses WebDriverIO v9 for automated screenshot capture across multiple browsers, viewport sizes, and device emulations. The capture system is designed to generate consistent visual regression testing screenshots by hooking into a test runner that iterates through configured pages and viewport configurations.
|
|
8
|
+
|
|
9
|
+
The `_Node/capture.js` file serves as the entry point for custom capture logic, allowing projects to extend the default screenshot behavior with page-specific interactions, element manipulation, and additional test scenarios.
|
|
10
|
+
|
|
11
|
+
## Folder Structure
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
_Node/
|
|
15
|
+
├── capture.js # Main capture customization file
|
|
16
|
+
├── capture-*.js # Additional capture files (if needed)
|
|
17
|
+
└── capture/ # Capture-related modules directory
|
|
18
|
+
└── **/*.js # Nested capture modules
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The capture system automatically loads the first available file:
|
|
22
|
+
|
|
23
|
+
1. `_Node/level-0/capture.js` (if exists)
|
|
24
|
+
2. `_Node/capture.js` (fallback)
|
|
25
|
+
|
|
26
|
+
## Libraries and Frameworks
|
|
27
|
+
|
|
28
|
+
### WebDriverIO v9
|
|
29
|
+
|
|
30
|
+
- **Browser Control**: Uses WebDriverIO's browser object for page interaction
|
|
31
|
+
- **Element Selection**: Supports CSS selectors and XPath for element targeting
|
|
32
|
+
- **Screenshot Capture**: Utilizes BiDi capabilities for full-page and viewport-only screenshots
|
|
33
|
+
- **Device Emulation**: Supports both manual viewport sizing and device emulation profiles
|
|
34
|
+
|
|
35
|
+
### Test Runner Integration
|
|
36
|
+
|
|
37
|
+
- Built on a test framework using `describe()` and `it()` blocks
|
|
38
|
+
- All custom interactions must be wrapped in `it()` test blocks
|
|
39
|
+
- Tests run asynchronously after being mapped out synchronously
|
|
40
|
+
|
|
41
|
+
## Coding Standards
|
|
42
|
+
|
|
43
|
+
### Module Structure
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
module.exports = {
|
|
47
|
+
// Called after viewport resize, before page iteration
|
|
48
|
+
size: (capture) => {
|
|
49
|
+
// Add dynamic pages to capture.page.array
|
|
50
|
+
// Access to capture.size properties
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// Called after default page capture for each page
|
|
54
|
+
page: (capture) => {
|
|
55
|
+
// Wrap all interactions in it() blocks
|
|
56
|
+
// Access to capture.page and capture.screenshot
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Error Handling
|
|
62
|
+
|
|
63
|
+
- Let screenshot processes fail if expected selectors don't exist
|
|
64
|
+
- Failing tests indicate markup changes that need attention
|
|
65
|
+
- Avoid silent failures that could mask site issues
|
|
66
|
+
|
|
67
|
+
### Asynchronous Operations
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// Always use async/await for browser interactions
|
|
71
|
+
it("Test Description", async () => {
|
|
72
|
+
await browser.click(".selector");
|
|
73
|
+
await browser.pause(1000);
|
|
74
|
+
await capture.screenshot.call();
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Element Waiting
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// Wait for specific elements before interaction
|
|
82
|
+
await $(".selector").waitForExist(50000);
|
|
83
|
+
|
|
84
|
+
// Use hard-coded waits sparingly
|
|
85
|
+
await browser.pause(1000);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Page-Specific Logic
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
// Target specific pages using route comparison
|
|
92
|
+
if (capture.page.route === "/") {
|
|
93
|
+
// Homepage-only logic
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (capture.page.route === "/products") {
|
|
97
|
+
// Products page logic
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## UI Guidelines
|
|
102
|
+
|
|
103
|
+
### Screenshot Capture Methods
|
|
104
|
+
|
|
105
|
+
#### Full Page Screenshots (Default)
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
// Captures entire scrollable page content
|
|
109
|
+
await capture.screenshot.call();
|
|
110
|
+
// or explicitly
|
|
111
|
+
await capture.screenshot.call(false);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Viewport-Only Screenshots
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
// Captures only visible viewport area
|
|
118
|
+
await capture.screenshot.call(true);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Common UI Interaction Patterns
|
|
122
|
+
|
|
123
|
+
#### Removing Overlay Elements
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
it("Remove Modal Overlays", async () => {
|
|
127
|
+
await browser.execute(() => {
|
|
128
|
+
// Remove ISI trays
|
|
129
|
+
const isi = document.querySelector(".cmp-isi-tray");
|
|
130
|
+
if (isi) isi.remove();
|
|
131
|
+
|
|
132
|
+
// Remove cookie banners
|
|
133
|
+
const cookies = document.querySelector(".cookie-banner");
|
|
134
|
+
if (cookies) cookies.remove();
|
|
135
|
+
|
|
136
|
+
// Remove any modal overlays
|
|
137
|
+
const modals = document.querySelectorAll(".modal-overlay, .popup");
|
|
138
|
+
modals.forEach((modal) => modal.remove());
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Carousel Interaction
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
it("Capture Carousel States", async () => {
|
|
147
|
+
const slides = await $$(".carousel-slide");
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < slides.length; i++) {
|
|
150
|
+
await browser.click(".carousel-next");
|
|
151
|
+
await browser.pause(500); // Allow transition
|
|
152
|
+
await capture.screenshot.call(true); // Viewport only
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Accordion Toggling
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
it("Capture Accordion States", async () => {
|
|
161
|
+
const accordions = await $$(".accordion-header");
|
|
162
|
+
|
|
163
|
+
for (const accordion of accordions) {
|
|
164
|
+
await accordion.click();
|
|
165
|
+
await browser.pause(300); // Allow expansion
|
|
166
|
+
await capture.screenshot.call(true); // Viewport only
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### Navigation States
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
it("Navigation Menu", async () => {
|
|
175
|
+
// Open navigation
|
|
176
|
+
await browser.click(".js-menu");
|
|
177
|
+
await $(".navigation").waitForDisplayed();
|
|
178
|
+
|
|
179
|
+
// Capture viewport only to avoid scrolling
|
|
180
|
+
await capture.screenshot.call(true);
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Dynamic Page Generation
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
size: (capture) => {
|
|
188
|
+
// Add dynamic routes from router configuration
|
|
189
|
+
const dynamicRoutes = require("../_Build/js/libs/routes.js");
|
|
190
|
+
|
|
191
|
+
dynamicRoutes.forEach((route) => {
|
|
192
|
+
capture.page.array.push(route.path);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Add programmatically generated pages
|
|
196
|
+
const productIds = ["product-1", "product-2", "product-3"];
|
|
197
|
+
productIds.forEach((id) => {
|
|
198
|
+
capture.page.array.push(`/products/${id}`);
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Responsive Element Handling
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
it("Handle Responsive Elements", async () => {
|
|
207
|
+
// Check if mobile navigation exists
|
|
208
|
+
const mobileNav = await $(".mobile-nav");
|
|
209
|
+
if (await mobileNav.isDisplayed()) {
|
|
210
|
+
await mobileNav.click();
|
|
211
|
+
await capture.screenshot.call(true);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check if desktop navigation exists
|
|
215
|
+
const desktopNav = await $(".desktop-nav");
|
|
216
|
+
if (await desktopNav.isDisplayed()) {
|
|
217
|
+
await desktopNav.click();
|
|
218
|
+
await capture.screenshot.call(true);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Best Practices
|
|
224
|
+
|
|
225
|
+
- Use descriptive `it()` block names for clear test identification
|
|
226
|
+
- Screenshot naming is handled automatically - don't override
|
|
227
|
+
- Prefer viewport screenshots (`true`) for UI state variations
|
|
228
|
+
- Use full page screenshots (`false`/omitted) for complete page captures
|
|
229
|
+
- Wait for elements/animations to complete before capturing
|
|
230
|
+
- Remove interfering UI elements (modals, banners) before main capture
|
|
231
|
+
- Leverage `capture.page.route` for page-specific logic
|
|
232
|
+
- Add dynamic pages in the `size()` method to ensure they're captured across all configurations
|
package/globals.js
CHANGED
|
@@ -557,18 +557,20 @@ if (!existsSync(path.join(cwd, "stylelint.config.js"))) {
|
|
|
557
557
|
);
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
+
// Copy AI instructions file
|
|
561
|
+
const destDir = path.join(cwd, ".github/instructions");
|
|
562
|
+
if (!existsSync(destDir)) {
|
|
563
|
+
mkdirSync(destDir, { recursive: true });
|
|
564
|
+
}
|
|
565
|
+
|
|
560
566
|
// Copy AI instructions file
|
|
561
567
|
if (
|
|
562
568
|
(platform === "laravel" && process.env.VERSION_LARAVEL === "10") ||
|
|
563
569
|
platform === "php"
|
|
564
570
|
) {
|
|
565
|
-
const destDir = path.join(cwd, ".github/copilot");
|
|
566
|
-
if (!existsSync(destDir)) {
|
|
567
|
-
mkdirSync(destDir, { recursive: true });
|
|
568
|
-
}
|
|
569
571
|
copyFileSync(
|
|
570
572
|
path.join(__dirname, "_Ai/laravel-12.md"),
|
|
571
|
-
path.join(destDir, "instructions.md"),
|
|
573
|
+
path.join(destDir, "laravel.instructions.md"),
|
|
572
574
|
);
|
|
573
575
|
}
|
|
574
576
|
|
|
@@ -577,16 +579,17 @@ if (
|
|
|
577
579
|
pkg?.dependencies?.["vue"] &&
|
|
578
580
|
semver.satisfies(semver.coerce(pkg.dependencies["vue"]), "3.x")
|
|
579
581
|
) {
|
|
580
|
-
const destDir = path.join(cwd, ".github/copilot");
|
|
581
|
-
if (!existsSync(destDir)) {
|
|
582
|
-
mkdirSync(destDir, { recursive: true });
|
|
583
|
-
}
|
|
584
582
|
copyFileSync(
|
|
585
583
|
path.join(__dirname, "_Ai/vue-3.md"),
|
|
586
|
-
path.join(destDir, "instructions.md"),
|
|
584
|
+
path.join(destDir, "vue.instructions.md"),
|
|
587
585
|
);
|
|
588
586
|
}
|
|
589
587
|
|
|
588
|
+
copyFileSync(
|
|
589
|
+
path.join(__dirname, "_Ai/webdriveriov9-capture.md"),
|
|
590
|
+
path.join(destDir, "webdriverio-capture.instructions.md"),
|
|
591
|
+
);
|
|
592
|
+
|
|
590
593
|
// If docker-compose.yml exists in project _Docker folder append to end
|
|
591
594
|
let localOverride = "";
|
|
592
595
|
if (existsSync(path.join(cwd, "_Docker/docker-compose.yml"))) {
|