@electriccitizen/bolt 0.1.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/README.md +361 -0
- package/dist/adapters/ddev.d.ts +16 -0
- package/dist/adapters/ddev.js +75 -0
- package/dist/adapters/ddev.js.map +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +167 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.js +263 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.js +319 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/pr.d.ts +20 -0
- package/dist/commands/pr.js +282 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/refresh.d.ts +22 -0
- package/dist/commands/refresh.js +375 -0
- package/dist/commands/refresh.js.map +1 -0
- package/dist/commands/suppress.d.ts +10 -0
- package/dist/commands/suppress.js +86 -0
- package/dist/commands/suppress.js.map +1 -0
- package/dist/commands/test.d.ts +5 -0
- package/dist/commands/test.js +106 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/update.d.ts +26 -0
- package/dist/commands/update.js +573 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +187 -0
- package/dist/config.js.map +1 -0
- package/dist/formatters/index.d.ts +2 -0
- package/dist/formatters/index.js +3 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/json.d.ts +5 -0
- package/dist/formatters/json.js +7 -0
- package/dist/formatters/json.js.map +1 -0
- package/dist/formatters/markdown.d.ts +5 -0
- package/dist/formatters/markdown.js +144 -0
- package/dist/formatters/markdown.js.map +1 -0
- package/dist/formatters/text.d.ts +5 -0
- package/dist/formatters/text.js +123 -0
- package/dist/formatters/text.js.map +1 -0
- package/dist/plugins/accessibility.d.ts +5 -0
- package/dist/plugins/accessibility.js +116 -0
- package/dist/plugins/accessibility.js.map +1 -0
- package/dist/plugins/browser-smoke.d.ts +6 -0
- package/dist/plugins/browser-smoke.js +331 -0
- package/dist/plugins/browser-smoke.js.map +1 -0
- package/dist/plugins/field-interaction.d.ts +6 -0
- package/dist/plugins/field-interaction.js +570 -0
- package/dist/plugins/field-interaction.js.map +1 -0
- package/dist/plugins/index.d.ts +6 -0
- package/dist/plugins/index.js +28 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/linkit.d.ts +8 -0
- package/dist/plugins/linkit.js +170 -0
- package/dist/plugins/linkit.js.map +1 -0
- package/dist/plugins/media-browser.d.ts +6 -0
- package/dist/plugins/media-browser.js +257 -0
- package/dist/plugins/media-browser.js.map +1 -0
- package/dist/plugins/structural-smoke.d.ts +6 -0
- package/dist/plugins/structural-smoke.js +90 -0
- package/dist/plugins/structural-smoke.js.map +1 -0
- package/dist/plugins/visual-regression.d.ts +8 -0
- package/dist/plugins/visual-regression.js +214 -0
- package/dist/plugins/visual-regression.js.map +1 -0
- package/dist/plugins/wysiwyg.d.ts +8 -0
- package/dist/plugins/wysiwyg.js +221 -0
- package/dist/plugins/wysiwyg.js.map +1 -0
- package/dist/runner.d.ts +21 -0
- package/dist/runner.js +293 -0
- package/dist/runner.js.map +1 -0
- package/dist/suppression.d.ts +55 -0
- package/dist/suppression.js +223 -0
- package/dist/suppression.js.map +1 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/modules/bolt_inspect/bolt_inspect.info.yml +6 -0
- package/modules/bolt_inspect/bolt_inspect.services.yml +22 -0
- package/modules/bolt_inspect/composer.json +16 -0
- package/modules/bolt_inspect/drush.services.yml +10 -0
- package/modules/bolt_inspect/src/Drush/Commands/BoltInspectCommands.php +203 -0
- package/modules/bolt_inspect/src/Service/ContentGenerator.php +586 -0
- package/modules/bolt_inspect/src/Service/SiteProfiler.php +362 -0
- package/modules/bolt_inspect/src/Service/TestEntityTracker.php +98 -0
- package/package.json +46 -0
- package/scripts/setup.sh +34 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* visual-regression plugin — screenshot comparison with pixelmatch.
|
|
3
|
+
*
|
|
4
|
+
* First run (no baseline): captures screenshots, reports as "skip" (baseline created).
|
|
5
|
+
* Subsequent runs: compares against baseline, produces diff images, reports pass/fail.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { PNG } from 'pngjs';
|
|
10
|
+
import _pixelmatch from 'pixelmatch';
|
|
11
|
+
const DEFAULT_VIEWPORTS = [
|
|
12
|
+
{ name: 'desktop', width: 1920, height: 1080 },
|
|
13
|
+
{ name: 'tablet', width: 768, height: 1024 },
|
|
14
|
+
{ name: 'mobile', width: 375, height: 812 },
|
|
15
|
+
];
|
|
16
|
+
const DEFAULT_THRESHOLD = 0.1; // percent
|
|
17
|
+
export const visualRegressionPlugin = {
|
|
18
|
+
name: 'visual-regression',
|
|
19
|
+
description: 'Screenshot comparison of representative URLs across viewports',
|
|
20
|
+
readOnly: true,
|
|
21
|
+
needsGeneratedContent: false,
|
|
22
|
+
canRun(profile) {
|
|
23
|
+
return profile.representativeUrls.length > 0;
|
|
24
|
+
},
|
|
25
|
+
async run(ctx) {
|
|
26
|
+
const results = [];
|
|
27
|
+
const { siteUrl, browserContext, profile, options } = ctx;
|
|
28
|
+
// Read config.
|
|
29
|
+
const pluginConfig = options.boltConfig?.plugins?.config?.['visual-regression'];
|
|
30
|
+
const threshold = options.vrThreshold ?? pluginConfig?.threshold ?? DEFAULT_THRESHOLD;
|
|
31
|
+
const baselineDir = options.vrBaseline;
|
|
32
|
+
const outputDir = options.screenshots ?? './vr-screenshots';
|
|
33
|
+
// Parse viewports from config or use defaults.
|
|
34
|
+
let viewports = DEFAULT_VIEWPORTS;
|
|
35
|
+
if (pluginConfig?.viewports && Array.isArray(pluginConfig.viewports)) {
|
|
36
|
+
viewports = pluginConfig.viewports.map((vp, i) => ({
|
|
37
|
+
name: i === 0 ? 'desktop' : i === 1 ? 'tablet' : 'mobile',
|
|
38
|
+
width: vp[0],
|
|
39
|
+
height: vp[1],
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
const maskSelectors = pluginConfig?.mask?.map((m) => m.selector) ?? [];
|
|
43
|
+
// Ensure output directory exists.
|
|
44
|
+
mkdirSync(outputDir, { recursive: true });
|
|
45
|
+
const hasBaseline = baselineDir && existsSync(baselineDir);
|
|
46
|
+
for (const rep of profile.representativeUrls) {
|
|
47
|
+
const fullUrl = rep.url.startsWith('http') ? rep.url : siteUrl + rep.url;
|
|
48
|
+
const slug = slugify(rep.label);
|
|
49
|
+
for (const viewport of viewports) {
|
|
50
|
+
const start = Date.now();
|
|
51
|
+
const filename = `${slug}-${viewport.name}.png`;
|
|
52
|
+
const currentPath = join(outputDir, filename);
|
|
53
|
+
let page;
|
|
54
|
+
try {
|
|
55
|
+
page = await browserContext.newPage();
|
|
56
|
+
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
|
57
|
+
await page.goto(fullUrl, { waitUntil: 'networkidle', timeout: 30_000 });
|
|
58
|
+
// Wait for images/fonts to settle.
|
|
59
|
+
try {
|
|
60
|
+
await page.waitForTimeout(500);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// non-fatal
|
|
64
|
+
}
|
|
65
|
+
// Mask dynamic elements before screenshot.
|
|
66
|
+
for (const selector of maskSelectors) {
|
|
67
|
+
await page.evaluate((sel) => {
|
|
68
|
+
document.querySelectorAll(sel).forEach((el) => {
|
|
69
|
+
el.style.visibility = 'hidden';
|
|
70
|
+
});
|
|
71
|
+
}, selector);
|
|
72
|
+
}
|
|
73
|
+
// Capture screenshot.
|
|
74
|
+
await page.screenshot({ path: currentPath, fullPage: true });
|
|
75
|
+
const duration = Date.now() - start;
|
|
76
|
+
if (!hasBaseline) {
|
|
77
|
+
// No baseline — save as baseline if baselineDir is set.
|
|
78
|
+
if (baselineDir) {
|
|
79
|
+
mkdirSync(baselineDir, { recursive: true });
|
|
80
|
+
const baselinePath = join(baselineDir, filename);
|
|
81
|
+
writeFileSync(baselinePath, readFileSync(currentPath));
|
|
82
|
+
}
|
|
83
|
+
results.push({
|
|
84
|
+
plugin: 'visual-regression',
|
|
85
|
+
test: `vr:${rep.label}:${viewport.name}`,
|
|
86
|
+
status: 'skip',
|
|
87
|
+
severity: 'info',
|
|
88
|
+
message: baselineDir
|
|
89
|
+
? `Baseline captured: ${filename}`
|
|
90
|
+
: `No --vr-baseline specified. Screenshot saved: ${filename}`,
|
|
91
|
+
duration,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Compare against baseline.
|
|
96
|
+
const baselinePath = join(baselineDir, filename);
|
|
97
|
+
if (!existsSync(baselinePath)) {
|
|
98
|
+
// New page/viewport not in baseline — capture and report.
|
|
99
|
+
results.push({
|
|
100
|
+
plugin: 'visual-regression',
|
|
101
|
+
test: `vr:${rep.label}:${viewport.name}`,
|
|
102
|
+
status: 'skip',
|
|
103
|
+
severity: 'info',
|
|
104
|
+
message: `No baseline for ${filename} — screenshot captured`,
|
|
105
|
+
duration,
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const diffResult = compareImages(baselinePath, currentPath, threshold);
|
|
110
|
+
if (diffResult.match) {
|
|
111
|
+
results.push({
|
|
112
|
+
plugin: 'visual-regression',
|
|
113
|
+
test: `vr:${rep.label}:${viewport.name}`,
|
|
114
|
+
status: 'pass',
|
|
115
|
+
severity: 'info',
|
|
116
|
+
message: `${viewport.name} ${diffResult.diffPercent.toFixed(3)}% diff (threshold: ${threshold}%)`,
|
|
117
|
+
duration,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Save diff image.
|
|
122
|
+
const diffFilename = `${slug}-${viewport.name}-diff.png`;
|
|
123
|
+
const diffPath = join(outputDir, diffFilename);
|
|
124
|
+
if (diffResult.diffPng) {
|
|
125
|
+
writeFileSync(diffPath, PNG.sync.write(diffResult.diffPng));
|
|
126
|
+
}
|
|
127
|
+
results.push({
|
|
128
|
+
plugin: 'visual-regression',
|
|
129
|
+
test: `vr:${rep.label}:${viewport.name}`,
|
|
130
|
+
status: 'fail',
|
|
131
|
+
severity: 'major',
|
|
132
|
+
message: `${fullUrl} — ${viewport.name} ${diffResult.diffPercent.toFixed(3)}% diff (threshold: ${threshold}%). Diff: ${diffFilename}`,
|
|
133
|
+
screenshot: diffPath,
|
|
134
|
+
duration,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const duration = Date.now() - start;
|
|
141
|
+
results.push({
|
|
142
|
+
plugin: 'visual-regression',
|
|
143
|
+
test: `vr:${rep.label}:${viewport.name}`,
|
|
144
|
+
status: 'fail',
|
|
145
|
+
severity: 'critical',
|
|
146
|
+
message: `Screenshot failed for ${rep.label} (${viewport.name}): ${err instanceof Error ? err.message : String(err)}`,
|
|
147
|
+
duration,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
if (page)
|
|
152
|
+
await page.close();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return results;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Compare two PNG images using pixelmatch.
|
|
161
|
+
* Handles size mismatches by padding the smaller image.
|
|
162
|
+
*/
|
|
163
|
+
function compareImages(baselinePath, currentPath, thresholdPercent) {
|
|
164
|
+
const baselineData = PNG.sync.read(readFileSync(baselinePath));
|
|
165
|
+
const currentData = PNG.sync.read(readFileSync(currentPath));
|
|
166
|
+
// Use the larger dimensions to accommodate size changes.
|
|
167
|
+
const width = Math.max(baselineData.width, currentData.width);
|
|
168
|
+
const height = Math.max(baselineData.height, currentData.height);
|
|
169
|
+
// Pad images to the same size if needed.
|
|
170
|
+
const baseline = padImage(baselineData, width, height);
|
|
171
|
+
const current = padImage(currentData, width, height);
|
|
172
|
+
const diff = new PNG({ width, height });
|
|
173
|
+
// Handle both ESM default and CJS export.
|
|
174
|
+
const pixelmatch = typeof _pixelmatch === 'function' ? _pixelmatch : _pixelmatch.default;
|
|
175
|
+
const diffPixels = pixelmatch(baseline.data, current.data, diff.data, width, height, { threshold: 0.1 });
|
|
176
|
+
const totalPixels = width * height;
|
|
177
|
+
const diffPercent = (diffPixels / totalPixels) * 100;
|
|
178
|
+
return {
|
|
179
|
+
match: diffPercent <= thresholdPercent,
|
|
180
|
+
diffPercent,
|
|
181
|
+
diffPixels,
|
|
182
|
+
diffPng: diffPixels > 0 ? diff : undefined,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Pad a PNG to the target dimensions (transparent padding).
|
|
187
|
+
*/
|
|
188
|
+
function padImage(img, targetWidth, targetHeight) {
|
|
189
|
+
if (img.width === targetWidth && img.height === targetHeight) {
|
|
190
|
+
return img;
|
|
191
|
+
}
|
|
192
|
+
const padded = new PNG({ width: targetWidth, height: targetHeight, fill: true });
|
|
193
|
+
// Fill with transparent pixels.
|
|
194
|
+
padded.data.fill(0);
|
|
195
|
+
// Copy original image data.
|
|
196
|
+
for (let y = 0; y < img.height; y++) {
|
|
197
|
+
for (let x = 0; x < img.width; x++) {
|
|
198
|
+
const srcIdx = (y * img.width + x) * 4;
|
|
199
|
+
const dstIdx = (y * targetWidth + x) * 4;
|
|
200
|
+
padded.data[dstIdx] = img.data[srcIdx];
|
|
201
|
+
padded.data[dstIdx + 1] = img.data[srcIdx + 1];
|
|
202
|
+
padded.data[dstIdx + 2] = img.data[srcIdx + 2];
|
|
203
|
+
padded.data[dstIdx + 3] = img.data[srcIdx + 3];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return padded;
|
|
207
|
+
}
|
|
208
|
+
function slugify(str) {
|
|
209
|
+
return str
|
|
210
|
+
.toLowerCase()
|
|
211
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
212
|
+
.replace(/^-|-$/g, '');
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=visual-regression.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-regression.js","sourceRoot":"","sources":["../../src/plugins/visual-regression.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wysiwyg plugin — tests CKEditor 5 toolbar operations on content edit forms.
|
|
3
|
+
*
|
|
4
|
+
* Verifies: editor loads, basic formatting works, link dialog opens,
|
|
5
|
+
* media embed dialog opens (if available).
|
|
6
|
+
*/
|
|
7
|
+
import type { TestPlugin } from '../types.js';
|
|
8
|
+
export declare const wysiwygPlugin: TestPlugin;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wysiwyg plugin — tests CKEditor 5 toolbar operations on content edit forms.
|
|
3
|
+
*
|
|
4
|
+
* Verifies: editor loads, basic formatting works, link dialog opens,
|
|
5
|
+
* media embed dialog opens (if available).
|
|
6
|
+
*/
|
|
7
|
+
export const wysiwygPlugin = {
|
|
8
|
+
name: 'wysiwyg',
|
|
9
|
+
description: 'Test CKEditor 5 toolbar rendering and basic operations',
|
|
10
|
+
readOnly: false,
|
|
11
|
+
needsGeneratedContent: false,
|
|
12
|
+
requiredModules: ['ckeditor5'],
|
|
13
|
+
dependsOn: ['structural-smoke'],
|
|
14
|
+
canRun(profile) {
|
|
15
|
+
// Need at least one content type with a text field that uses CKEditor.
|
|
16
|
+
return profile.contentTypes.some((ct) => ct.fields.some((f) => f.type === 'text_long' || f.type === 'text_with_summary'));
|
|
17
|
+
},
|
|
18
|
+
async run(ctx) {
|
|
19
|
+
const results = [];
|
|
20
|
+
const { siteUrl, browserContext, profile } = ctx;
|
|
21
|
+
// Find a content type with a CKEditor field to test on.
|
|
22
|
+
const testType = profile.contentTypes.find((ct) => ct.fields.some((f) => f.type === 'text_long' || f.type === 'text_with_summary'));
|
|
23
|
+
if (!testType) {
|
|
24
|
+
results.push({
|
|
25
|
+
plugin: 'wysiwyg',
|
|
26
|
+
test: 'editor:no-type',
|
|
27
|
+
status: 'skip',
|
|
28
|
+
severity: 'info',
|
|
29
|
+
message: 'No content type with a text_long/text_with_summary field found',
|
|
30
|
+
duration: 0,
|
|
31
|
+
});
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
const addUrl = `${siteUrl}/node/add/${testType.id}`;
|
|
35
|
+
let page;
|
|
36
|
+
try {
|
|
37
|
+
page = await browserContext.newPage();
|
|
38
|
+
await page.goto(addUrl, { waitUntil: 'networkidle', timeout: 30_000 });
|
|
39
|
+
// Test 1: CKEditor loads and toolbar renders.
|
|
40
|
+
{
|
|
41
|
+
const start = Date.now();
|
|
42
|
+
const toolbar = page.locator('.ck-toolbar');
|
|
43
|
+
const editorLoaded = await toolbar.count() > 0;
|
|
44
|
+
const duration = Date.now() - start;
|
|
45
|
+
if (editorLoaded) {
|
|
46
|
+
const buttonCount = await page.locator('.ck-toolbar .ck-button').count();
|
|
47
|
+
results.push({
|
|
48
|
+
plugin: 'wysiwyg',
|
|
49
|
+
test: 'editor:toolbar-loads',
|
|
50
|
+
status: 'pass',
|
|
51
|
+
severity: 'info',
|
|
52
|
+
message: `CKEditor toolbar rendered with ${buttonCount} buttons`,
|
|
53
|
+
duration,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
results.push({
|
|
58
|
+
plugin: 'wysiwyg',
|
|
59
|
+
test: 'editor:toolbar-loads',
|
|
60
|
+
status: 'fail',
|
|
61
|
+
severity: 'critical',
|
|
62
|
+
message: `${addUrl} — CKEditor toolbar (.ck-toolbar) not found`,
|
|
63
|
+
duration,
|
|
64
|
+
});
|
|
65
|
+
return results; // No point testing further.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Find the editable area.
|
|
69
|
+
const editable = page.locator('.ck-editor__editable[contenteditable="true"]').first();
|
|
70
|
+
if (await editable.count() === 0) {
|
|
71
|
+
results.push({
|
|
72
|
+
plugin: 'wysiwyg',
|
|
73
|
+
test: 'editor:editable-area',
|
|
74
|
+
status: 'fail',
|
|
75
|
+
severity: 'critical',
|
|
76
|
+
message: `${addUrl} — CKEditor editable area not found`,
|
|
77
|
+
duration: 0,
|
|
78
|
+
});
|
|
79
|
+
return results;
|
|
80
|
+
}
|
|
81
|
+
// Test 2: Text input works.
|
|
82
|
+
{
|
|
83
|
+
const start = Date.now();
|
|
84
|
+
await editable.click();
|
|
85
|
+
await editable.pressSequentially('Hello from Bolt test.', { delay: 20 });
|
|
86
|
+
const content = await editable.textContent();
|
|
87
|
+
const hasText = content?.includes('Hello from Bolt test');
|
|
88
|
+
const duration = Date.now() - start;
|
|
89
|
+
results.push({
|
|
90
|
+
plugin: 'wysiwyg',
|
|
91
|
+
test: 'editor:text-input',
|
|
92
|
+
status: hasText ? 'pass' : 'fail',
|
|
93
|
+
severity: hasText ? 'info' : 'major',
|
|
94
|
+
message: hasText
|
|
95
|
+
? 'Text input into CKEditor works'
|
|
96
|
+
: `${addUrl} — Text not reflected in CKEditor editable area`,
|
|
97
|
+
duration,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Test 3: Bold formatting.
|
|
101
|
+
{
|
|
102
|
+
const start = Date.now();
|
|
103
|
+
// Select all text and apply bold.
|
|
104
|
+
await editable.click();
|
|
105
|
+
await page.keyboard.press('Control+a');
|
|
106
|
+
const boldButton = page.locator('.ck-toolbar button[data-cke-tooltip-text*="Bold"], .ck-toolbar button.ck-button:has(.ck-icon[viewBox])').first();
|
|
107
|
+
let boldWorks = false;
|
|
108
|
+
if (await boldButton.count() > 0) {
|
|
109
|
+
await boldButton.click();
|
|
110
|
+
// Check if <strong> tag was added.
|
|
111
|
+
const html = await editable.innerHTML();
|
|
112
|
+
boldWorks = html.includes('<strong>') || html.includes('font-weight');
|
|
113
|
+
// Undo bold.
|
|
114
|
+
await boldButton.click();
|
|
115
|
+
}
|
|
116
|
+
const duration = Date.now() - start;
|
|
117
|
+
results.push({
|
|
118
|
+
plugin: 'wysiwyg',
|
|
119
|
+
test: 'editor:bold',
|
|
120
|
+
status: boldWorks ? 'pass' : 'warn',
|
|
121
|
+
severity: boldWorks ? 'info' : 'minor',
|
|
122
|
+
message: boldWorks
|
|
123
|
+
? 'Bold formatting applies correctly'
|
|
124
|
+
: 'Bold button not found or formatting not applied',
|
|
125
|
+
duration,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Test 4: Link dialog opens.
|
|
129
|
+
{
|
|
130
|
+
const start = Date.now();
|
|
131
|
+
await editable.click();
|
|
132
|
+
await page.keyboard.press('Control+a');
|
|
133
|
+
// Try to find the link button.
|
|
134
|
+
const linkButton = page.locator('.ck-toolbar button[data-cke-tooltip-text*="Link"], ' +
|
|
135
|
+
'.ck-toolbar button[data-cke-tooltip-text*="link"]').first();
|
|
136
|
+
let linkDialogOpens = false;
|
|
137
|
+
if (await linkButton.count() > 0) {
|
|
138
|
+
await linkButton.click();
|
|
139
|
+
await page.waitForTimeout(1000);
|
|
140
|
+
// Check for link form/balloon.
|
|
141
|
+
const linkForm = page.locator('.ck-link-form, .ck-balloon-panel, .ck-labeled-field-view');
|
|
142
|
+
linkDialogOpens = await linkForm.count() > 0;
|
|
143
|
+
// Close the dialog.
|
|
144
|
+
await page.keyboard.press('Escape');
|
|
145
|
+
await page.waitForTimeout(500);
|
|
146
|
+
}
|
|
147
|
+
const duration = Date.now() - start;
|
|
148
|
+
results.push({
|
|
149
|
+
plugin: 'wysiwyg',
|
|
150
|
+
test: 'editor:link-dialog',
|
|
151
|
+
status: linkDialogOpens ? 'pass' : 'warn',
|
|
152
|
+
severity: linkDialogOpens ? 'info' : 'minor',
|
|
153
|
+
message: linkDialogOpens
|
|
154
|
+
? 'Link dialog/balloon opens correctly'
|
|
155
|
+
: 'Link button not found or dialog did not open',
|
|
156
|
+
duration,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Test 5: Media embed button (if present in toolbar).
|
|
160
|
+
{
|
|
161
|
+
const start = Date.now();
|
|
162
|
+
const mediaButton = page.locator('.ck-toolbar button[data-cke-tooltip-text*="media" i], ' +
|
|
163
|
+
'.ck-toolbar button[data-cke-tooltip-text*="Media"]').first();
|
|
164
|
+
if (await mediaButton.count() > 0) {
|
|
165
|
+
await mediaButton.click();
|
|
166
|
+
await page.waitForTimeout(2000);
|
|
167
|
+
// Check if media library dialog opened.
|
|
168
|
+
const mediaDialog = page.locator('.ui-dialog.media-library-widget-modal, ' +
|
|
169
|
+
'.media-library-widget-modal, ' +
|
|
170
|
+
'.ui-dialog[aria-describedby="drupal-modal"]');
|
|
171
|
+
const dialogOpened = await mediaDialog.count() > 0;
|
|
172
|
+
const duration = Date.now() - start;
|
|
173
|
+
// Close the dialog.
|
|
174
|
+
if (dialogOpened) {
|
|
175
|
+
const closeBtn = page.locator('.ui-dialog-titlebar-close').first();
|
|
176
|
+
if (await closeBtn.count() > 0) {
|
|
177
|
+
await closeBtn.click();
|
|
178
|
+
await page.waitForTimeout(500);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
results.push({
|
|
182
|
+
plugin: 'wysiwyg',
|
|
183
|
+
test: 'editor:media-embed',
|
|
184
|
+
status: dialogOpened ? 'pass' : 'fail',
|
|
185
|
+
severity: dialogOpened ? 'info' : 'major',
|
|
186
|
+
message: dialogOpened
|
|
187
|
+
? 'Media embed dialog opens from CKEditor toolbar'
|
|
188
|
+
: `${addUrl} — Media embed button clicked but dialog did not open`,
|
|
189
|
+
duration,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
results.push({
|
|
194
|
+
plugin: 'wysiwyg',
|
|
195
|
+
test: 'editor:media-embed',
|
|
196
|
+
status: 'skip',
|
|
197
|
+
severity: 'info',
|
|
198
|
+
message: 'No media embed button in CKEditor toolbar',
|
|
199
|
+
duration: Date.now() - start,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
results.push({
|
|
206
|
+
plugin: 'wysiwyg',
|
|
207
|
+
test: 'editor:crash',
|
|
208
|
+
status: 'fail',
|
|
209
|
+
severity: 'critical',
|
|
210
|
+
message: `WYSIWYG test failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
211
|
+
duration: 0,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
if (page)
|
|
216
|
+
await page.close();
|
|
217
|
+
}
|
|
218
|
+
return results;
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
//# sourceMappingURL=wysiwyg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wysiwyg.js","sourceRoot":"","sources":["../../src/plugins/wysiwyg.ts"],"names":[],"mappings":"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"}
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test runner — discovers plugins, fetches site profile, runs tests.
|
|
3
|
+
*/
|
|
4
|
+
import type { ExecutionMode, HostingAdapter, PluginSummary, SiteProfile, TestOptions, TestPlugin, TestReport } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Filter and sort plugins based on mode, profile, and user selection.
|
|
7
|
+
*/
|
|
8
|
+
/** @internal Exported for testing. */
|
|
9
|
+
export declare function selectPlugins(allPlugins: TestPlugin[], profile: SiteProfile, mode: ExecutionMode, selected?: string[], configSkip?: string[]): {
|
|
10
|
+
run: TestPlugin[];
|
|
11
|
+
skipped: PluginSummary[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Topological sort with cycle detection.
|
|
15
|
+
*/
|
|
16
|
+
/** @internal Exported for testing. */
|
|
17
|
+
export declare function topoSort(plugins: TestPlugin[]): TestPlugin[];
|
|
18
|
+
/**
|
|
19
|
+
* Run the full test pipeline.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runTests(adapter: HostingAdapter, options: TestOptions): Promise<TestReport>;
|