@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,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* linkit plugin — tests the Linkit autocomplete integration with CKEditor 5.
|
|
3
|
+
*
|
|
4
|
+
* Verifies: link dialog opens, Linkit autocomplete field is present,
|
|
5
|
+
* typing triggers suggestions, selecting a suggestion populates the URL.
|
|
6
|
+
*/
|
|
7
|
+
export const linkitPlugin = {
|
|
8
|
+
name: 'linkit',
|
|
9
|
+
description: 'Test Linkit autocomplete in CKEditor link dialog',
|
|
10
|
+
readOnly: false,
|
|
11
|
+
needsGeneratedContent: false,
|
|
12
|
+
requiredModules: ['linkit', 'ckeditor5'],
|
|
13
|
+
dependsOn: ['wysiwyg'],
|
|
14
|
+
canRun(profile) {
|
|
15
|
+
return profile.contentTypes.some((ct) => ct.fields.some((f) => f.type === 'text_long' || f.type === 'text_with_summary'));
|
|
16
|
+
},
|
|
17
|
+
async run(ctx) {
|
|
18
|
+
const results = [];
|
|
19
|
+
const { siteUrl, browserContext, profile } = ctx;
|
|
20
|
+
// Find a content type with a CKEditor field.
|
|
21
|
+
const testType = profile.contentTypes.find((ct) => ct.fields.some((f) => f.type === 'text_long' || f.type === 'text_with_summary'));
|
|
22
|
+
if (!testType) {
|
|
23
|
+
results.push({
|
|
24
|
+
plugin: 'linkit',
|
|
25
|
+
test: 'linkit:no-type',
|
|
26
|
+
status: 'skip',
|
|
27
|
+
severity: 'info',
|
|
28
|
+
message: 'No content type with a CKEditor field found',
|
|
29
|
+
duration: 0,
|
|
30
|
+
});
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
33
|
+
const addUrl = `${siteUrl}/node/add/${testType.id}`;
|
|
34
|
+
let page;
|
|
35
|
+
try {
|
|
36
|
+
page = await browserContext.newPage();
|
|
37
|
+
await page.goto(addUrl, { waitUntil: 'networkidle', timeout: 30_000 });
|
|
38
|
+
// Wait for CKEditor to initialize.
|
|
39
|
+
const editable = page.locator('.ck-editor__editable[contenteditable="true"]').first();
|
|
40
|
+
if (await editable.count() === 0) {
|
|
41
|
+
results.push({
|
|
42
|
+
plugin: 'linkit',
|
|
43
|
+
test: 'linkit:no-editor',
|
|
44
|
+
status: 'skip',
|
|
45
|
+
severity: 'info',
|
|
46
|
+
message: 'CKEditor not found on this form',
|
|
47
|
+
duration: 0,
|
|
48
|
+
});
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
// Type some text to select for linking.
|
|
52
|
+
await editable.click();
|
|
53
|
+
await editable.pressSequentially('Link test text', { delay: 20 });
|
|
54
|
+
await page.keyboard.press('Control+a');
|
|
55
|
+
// Test 1: Open link dialog and check for Linkit field.
|
|
56
|
+
{
|
|
57
|
+
const start = Date.now();
|
|
58
|
+
// Click the link button.
|
|
59
|
+
const linkButton = page.locator('.ck-toolbar button[data-cke-tooltip-text*="Link"], ' +
|
|
60
|
+
'.ck-toolbar button[data-cke-tooltip-text*="link"]').first();
|
|
61
|
+
if (await linkButton.count() === 0) {
|
|
62
|
+
results.push({
|
|
63
|
+
plugin: 'linkit',
|
|
64
|
+
test: 'linkit:link-button',
|
|
65
|
+
status: 'fail',
|
|
66
|
+
severity: 'major',
|
|
67
|
+
message: `${addUrl} — Link button not found in CKEditor toolbar`,
|
|
68
|
+
duration: Date.now() - start,
|
|
69
|
+
});
|
|
70
|
+
return results;
|
|
71
|
+
}
|
|
72
|
+
await linkButton.click();
|
|
73
|
+
await page.waitForTimeout(1500);
|
|
74
|
+
// Check for the link input (CKEditor's link balloon or Linkit's enhanced input).
|
|
75
|
+
const linkInput = page.locator('.ck-link-form .ck-input, ' +
|
|
76
|
+
'.ck-balloon-panel .ck-input, ' +
|
|
77
|
+
'.ck-labeled-field-view .ck-input').first();
|
|
78
|
+
const hasLinkInput = await linkInput.count() > 0;
|
|
79
|
+
const duration = Date.now() - start;
|
|
80
|
+
results.push({
|
|
81
|
+
plugin: 'linkit',
|
|
82
|
+
test: 'linkit:dialog-opens',
|
|
83
|
+
status: hasLinkInput ? 'pass' : 'fail',
|
|
84
|
+
severity: hasLinkInput ? 'info' : 'major',
|
|
85
|
+
message: hasLinkInput
|
|
86
|
+
? 'Link dialog opens with URL input field'
|
|
87
|
+
: `${addUrl} — Link dialog opened but no input field found`,
|
|
88
|
+
duration,
|
|
89
|
+
});
|
|
90
|
+
if (!hasLinkInput) {
|
|
91
|
+
await page.keyboard.press('Escape');
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
// Test 2: Linkit autocomplete — type a query and check for suggestions.
|
|
95
|
+
{
|
|
96
|
+
const start2 = Date.now();
|
|
97
|
+
await linkInput.fill('');
|
|
98
|
+
await linkInput.pressSequentially('about', { delay: 100 });
|
|
99
|
+
await page.waitForTimeout(2000);
|
|
100
|
+
// Linkit renders suggestions in a dropdown/autocomplete list.
|
|
101
|
+
const suggestions = page.locator('.ck-link-form .linkit-result, ' +
|
|
102
|
+
'.ck-balloon-panel .linkit-result, ' +
|
|
103
|
+
'.linkit-result-line, ' +
|
|
104
|
+
'.ck-reset_all .ck-list__item, ' +
|
|
105
|
+
'[class*="linkit"] li, ' +
|
|
106
|
+
'.ui-autocomplete li');
|
|
107
|
+
const suggestionCount = await suggestions.count();
|
|
108
|
+
const duration2 = Date.now() - start2;
|
|
109
|
+
if (suggestionCount > 0) {
|
|
110
|
+
// Test 3: Select a suggestion.
|
|
111
|
+
const selectStart = Date.now();
|
|
112
|
+
await suggestions.first().click();
|
|
113
|
+
await page.waitForTimeout(500);
|
|
114
|
+
// Check that the input now has a value.
|
|
115
|
+
const linkValue = await linkInput.inputValue().catch(() => '');
|
|
116
|
+
const hasValue = linkValue.length > 0;
|
|
117
|
+
const selectDuration = Date.now() - selectStart;
|
|
118
|
+
results.push({
|
|
119
|
+
plugin: 'linkit',
|
|
120
|
+
test: 'linkit:autocomplete',
|
|
121
|
+
status: 'pass',
|
|
122
|
+
severity: 'info',
|
|
123
|
+
message: `Linkit autocomplete returned ${suggestionCount} result(s) for "about"`,
|
|
124
|
+
duration: duration2,
|
|
125
|
+
});
|
|
126
|
+
results.push({
|
|
127
|
+
plugin: 'linkit',
|
|
128
|
+
test: 'linkit:selection',
|
|
129
|
+
status: hasValue ? 'pass' : 'warn',
|
|
130
|
+
severity: hasValue ? 'info' : 'minor',
|
|
131
|
+
message: hasValue
|
|
132
|
+
? `Linkit selection populated URL: ${linkValue.slice(0, 80)}`
|
|
133
|
+
: 'Linkit suggestion clicked but URL input not populated',
|
|
134
|
+
duration: selectDuration,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
results.push({
|
|
139
|
+
plugin: 'linkit',
|
|
140
|
+
test: 'linkit:autocomplete',
|
|
141
|
+
status: 'warn',
|
|
142
|
+
severity: 'minor',
|
|
143
|
+
message: 'Linkit autocomplete did not return suggestions for "about" — may not be configured for this text format',
|
|
144
|
+
duration: duration2,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Close the link dialog.
|
|
149
|
+
await page.keyboard.press('Escape');
|
|
150
|
+
await page.waitForTimeout(500);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
results.push({
|
|
155
|
+
plugin: 'linkit',
|
|
156
|
+
test: 'linkit:crash',
|
|
157
|
+
status: 'fail',
|
|
158
|
+
severity: 'critical',
|
|
159
|
+
message: `Linkit test failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
160
|
+
duration: 0,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
if (page)
|
|
165
|
+
await page.close();
|
|
166
|
+
}
|
|
167
|
+
return results;
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
//# sourceMappingURL=linkit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linkit.js","sourceRoot":"","sources":["../../src/plugins/linkit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* media-browser plugin — tests the media library upload and selection workflow
|
|
3
|
+
* for each media type on the site.
|
|
4
|
+
*/
|
|
5
|
+
/** Media type to test file mapping. */
|
|
6
|
+
const MEDIA_TYPE_FILES = {
|
|
7
|
+
image: {
|
|
8
|
+
name: 'bolt-test.png',
|
|
9
|
+
mime: 'image/png',
|
|
10
|
+
createBuffer: createMinimalPng,
|
|
11
|
+
},
|
|
12
|
+
document: {
|
|
13
|
+
name: 'bolt-test.txt',
|
|
14
|
+
mime: 'text/plain',
|
|
15
|
+
createBuffer: () => Buffer.from('Bolt test document content.'),
|
|
16
|
+
},
|
|
17
|
+
icon: {
|
|
18
|
+
name: 'bolt-test-icon.svg',
|
|
19
|
+
mime: 'image/svg+xml',
|
|
20
|
+
createBuffer: () => Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><circle cx="12" cy="12" r="10" fill="#333"/></svg>'),
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export const mediaBrowserPlugin = {
|
|
24
|
+
name: 'media-browser',
|
|
25
|
+
description: 'Test media library upload and selection for each media type',
|
|
26
|
+
readOnly: false,
|
|
27
|
+
needsGeneratedContent: false,
|
|
28
|
+
requiredModules: ['media', 'media_library'],
|
|
29
|
+
canRun(profile) {
|
|
30
|
+
return profile.mediaTypes.length > 0;
|
|
31
|
+
},
|
|
32
|
+
async run(ctx) {
|
|
33
|
+
const results = [];
|
|
34
|
+
const { siteUrl, browserContext, profile } = ctx;
|
|
35
|
+
// Test 1: Verify media library page loads.
|
|
36
|
+
{
|
|
37
|
+
const start = Date.now();
|
|
38
|
+
let page;
|
|
39
|
+
try {
|
|
40
|
+
page = await browserContext.newPage();
|
|
41
|
+
const response = await page.goto(`${siteUrl}/admin/content/media`, {
|
|
42
|
+
waitUntil: 'networkidle',
|
|
43
|
+
timeout: 30_000,
|
|
44
|
+
});
|
|
45
|
+
const duration = Date.now() - start;
|
|
46
|
+
if (!response || response.status() >= 400) {
|
|
47
|
+
results.push({
|
|
48
|
+
plugin: 'media-browser',
|
|
49
|
+
test: 'media-library:page',
|
|
50
|
+
status: 'fail',
|
|
51
|
+
severity: 'critical',
|
|
52
|
+
message: `${siteUrl}/admin/content/media — HTTP ${response?.status() ?? 0}`,
|
|
53
|
+
duration,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
results.push({
|
|
58
|
+
plugin: 'media-browser',
|
|
59
|
+
test: 'media-library:page',
|
|
60
|
+
status: 'pass',
|
|
61
|
+
severity: 'info',
|
|
62
|
+
message: 'Media library page loads OK',
|
|
63
|
+
duration,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
results.push({
|
|
69
|
+
plugin: 'media-browser',
|
|
70
|
+
test: 'media-library:page',
|
|
71
|
+
status: 'fail',
|
|
72
|
+
severity: 'critical',
|
|
73
|
+
message: `Media library page failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
74
|
+
duration: Date.now() - start,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
if (page)
|
|
79
|
+
await page.close();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Test 2: Add media for each uploadable type.
|
|
83
|
+
for (const mediaType of profile.mediaTypes) {
|
|
84
|
+
const start = Date.now();
|
|
85
|
+
let page;
|
|
86
|
+
// Skip remote_video — requires a URL, not a file upload.
|
|
87
|
+
if (mediaType === 'remote_video') {
|
|
88
|
+
results.push({
|
|
89
|
+
plugin: 'media-browser',
|
|
90
|
+
test: `media-add:${mediaType}`,
|
|
91
|
+
status: 'skip',
|
|
92
|
+
severity: 'info',
|
|
93
|
+
message: `${mediaType} — skipped (remote URL type)`,
|
|
94
|
+
duration: 0,
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
page = await browserContext.newPage();
|
|
100
|
+
// Navigate to media add form.
|
|
101
|
+
const addUrl = `${siteUrl}/media/add/${mediaType}`;
|
|
102
|
+
const response = await page.goto(addUrl, {
|
|
103
|
+
waitUntil: 'networkidle',
|
|
104
|
+
timeout: 30_000,
|
|
105
|
+
});
|
|
106
|
+
if (!response || response.status() >= 400) {
|
|
107
|
+
results.push({
|
|
108
|
+
plugin: 'media-browser',
|
|
109
|
+
test: `media-add:${mediaType}`,
|
|
110
|
+
status: 'fail',
|
|
111
|
+
severity: 'major',
|
|
112
|
+
message: `${addUrl} — HTTP ${response?.status() ?? 0}`,
|
|
113
|
+
duration: Date.now() - start,
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// Fill the name field if present.
|
|
118
|
+
const nameInput = page.locator('#edit-name-0-value, [name="name[0][value]"]').first();
|
|
119
|
+
if (await nameInput.count() > 0 && await nameInput.isVisible()) {
|
|
120
|
+
await nameInput.fill(`Bolt test ${mediaType}`);
|
|
121
|
+
}
|
|
122
|
+
// Upload a file.
|
|
123
|
+
const fileConfig = MEDIA_TYPE_FILES[mediaType];
|
|
124
|
+
if (fileConfig) {
|
|
125
|
+
const fileInput = page.locator('input[type="file"]').first();
|
|
126
|
+
if (await fileInput.count() > 0) {
|
|
127
|
+
await fileInput.setInputFiles({
|
|
128
|
+
name: fileConfig.name,
|
|
129
|
+
mimeType: fileConfig.mime,
|
|
130
|
+
buffer: fileConfig.createBuffer(),
|
|
131
|
+
});
|
|
132
|
+
// Wait for upload AJAX to complete.
|
|
133
|
+
await page.waitForTimeout(3000);
|
|
134
|
+
// Fill alt text for image-based media types.
|
|
135
|
+
if (mediaType === 'image' || mediaType === 'icon') {
|
|
136
|
+
// Wait for alt field to appear after AJAX upload.
|
|
137
|
+
await page.waitForTimeout(1000);
|
|
138
|
+
const altInput = page.locator('[name*="[alt]"], [data-drupal-selector*="alt"], input[id*="-alt"]').first();
|
|
139
|
+
if (await altInput.count() > 0 && await altInput.isVisible()) {
|
|
140
|
+
await altInput.fill('Bolt test media');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Submit.
|
|
146
|
+
const saveButton = page.locator('#edit-submit, input[value="Save"], button:text("Save")').first();
|
|
147
|
+
if (await saveButton.count() > 0) {
|
|
148
|
+
await saveButton.click();
|
|
149
|
+
await page.waitForTimeout(3000);
|
|
150
|
+
}
|
|
151
|
+
// Check result.
|
|
152
|
+
const hasError = await page.locator('.messages--error, .messages.error').count();
|
|
153
|
+
const hasSuccess = await page.locator('.messages--status, .messages.status').count();
|
|
154
|
+
const duration = Date.now() - start;
|
|
155
|
+
if (hasError > 0) {
|
|
156
|
+
const errorText = await page
|
|
157
|
+
.locator('.messages--error, .messages.error')
|
|
158
|
+
.first()
|
|
159
|
+
.textContent()
|
|
160
|
+
.catch(() => 'Unknown error');
|
|
161
|
+
results.push({
|
|
162
|
+
plugin: 'media-browser',
|
|
163
|
+
test: `media-add:${mediaType}`,
|
|
164
|
+
status: 'fail',
|
|
165
|
+
severity: 'major',
|
|
166
|
+
message: `${addUrl} — Upload error: ${(errorText ?? '').trim().slice(0, 200)}`,
|
|
167
|
+
duration,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
else if (hasSuccess > 0 || !page.url().includes('/media/add/')) {
|
|
171
|
+
// Clean up — delete the media we just created.
|
|
172
|
+
await cleanupMedia(page, siteUrl);
|
|
173
|
+
results.push({
|
|
174
|
+
plugin: 'media-browser',
|
|
175
|
+
test: `media-add:${mediaType}`,
|
|
176
|
+
status: 'pass',
|
|
177
|
+
severity: 'info',
|
|
178
|
+
message: `${mediaType} — upload + save OK`,
|
|
179
|
+
duration,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
results.push({
|
|
184
|
+
plugin: 'media-browser',
|
|
185
|
+
test: `media-add:${mediaType}`,
|
|
186
|
+
status: 'fail',
|
|
187
|
+
severity: 'major',
|
|
188
|
+
message: `${addUrl} — Form submitted but no success/error message`,
|
|
189
|
+
duration,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
results.push({
|
|
195
|
+
plugin: 'media-browser',
|
|
196
|
+
test: `media-add:${mediaType}`,
|
|
197
|
+
status: 'fail',
|
|
198
|
+
severity: 'critical',
|
|
199
|
+
message: `Media add failed for ${mediaType}: ${err instanceof Error ? err.message : String(err)}`,
|
|
200
|
+
duration: Date.now() - start,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
if (page)
|
|
205
|
+
await page.close();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return results;
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Clean up a just-created media entity by navigating to its delete page.
|
|
213
|
+
*/
|
|
214
|
+
async function cleanupMedia(page, siteUrl) {
|
|
215
|
+
try {
|
|
216
|
+
const currentUrl = page.url();
|
|
217
|
+
// After save, Drupal usually redirects to the media view or collection.
|
|
218
|
+
// Try to find the delete link for the media we just created.
|
|
219
|
+
const deleteLink = page.locator('a:text("Delete"), .dropbutton a:text("Delete")').first();
|
|
220
|
+
if (await deleteLink.count() > 0) {
|
|
221
|
+
await deleteLink.click();
|
|
222
|
+
await page.waitForTimeout(1000);
|
|
223
|
+
const confirmBtn = page.locator('#edit-submit, input[value="Delete"], button:text("Delete")').first();
|
|
224
|
+
if (await confirmBtn.count() > 0) {
|
|
225
|
+
await confirmBtn.click();
|
|
226
|
+
await page.waitForTimeout(1000);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Cleanup failure is non-fatal.
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Create a minimal valid 1×1 PNG.
|
|
236
|
+
*/
|
|
237
|
+
function createMinimalPng() {
|
|
238
|
+
return Buffer.from([
|
|
239
|
+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
|
|
240
|
+
0x00, 0x00, 0x00, 0x0d,
|
|
241
|
+
0x49, 0x48, 0x44, 0x52,
|
|
242
|
+
0x00, 0x00, 0x00, 0x01,
|
|
243
|
+
0x00, 0x00, 0x00, 0x01,
|
|
244
|
+
0x08, 0x02,
|
|
245
|
+
0x00, 0x00, 0x00,
|
|
246
|
+
0x90, 0x77, 0x53, 0xde,
|
|
247
|
+
0x00, 0x00, 0x00, 0x0c,
|
|
248
|
+
0x49, 0x44, 0x41, 0x54,
|
|
249
|
+
0x08, 0xd7, 0x63, 0xf8, 0xcf, 0xc0, 0x00, 0x00,
|
|
250
|
+
0x00, 0x02, 0x00, 0x01,
|
|
251
|
+
0xe2, 0x21, 0xbc, 0x33,
|
|
252
|
+
0x00, 0x00, 0x00, 0x00,
|
|
253
|
+
0x49, 0x45, 0x4e, 0x44,
|
|
254
|
+
0xae, 0x42, 0x60, 0x82,
|
|
255
|
+
]);
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=media-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-browser.js","sourceRoot":"","sources":["../../src/plugins/media-browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* structural-smoke plugin — verifies every content type can be created
|
|
3
|
+
* and rendered without errors via bolt_inspect Drush commands.
|
|
4
|
+
*/
|
|
5
|
+
export const structuralSmokePlugin = {
|
|
6
|
+
name: 'structural-smoke',
|
|
7
|
+
description: 'Create and render nodes for every content type via Drush',
|
|
8
|
+
requiredModules: ['bolt_inspect'],
|
|
9
|
+
needsGeneratedContent: true,
|
|
10
|
+
readOnly: false,
|
|
11
|
+
canRun(_profile) {
|
|
12
|
+
return true;
|
|
13
|
+
},
|
|
14
|
+
async run(ctx) {
|
|
15
|
+
const results = [];
|
|
16
|
+
const { adapter } = ctx;
|
|
17
|
+
const start = Date.now();
|
|
18
|
+
// Run the dedicated render-check command (returns clean JSON).
|
|
19
|
+
const renderResult = await adapter.drush('bolt-inspect:render-check');
|
|
20
|
+
if (renderResult.exitCode !== 0) {
|
|
21
|
+
return [{
|
|
22
|
+
plugin: 'structural-smoke',
|
|
23
|
+
test: 'render-check',
|
|
24
|
+
status: 'fail',
|
|
25
|
+
severity: 'critical',
|
|
26
|
+
message: `render-check command failed: ${renderResult.stderr.slice(0, 500)}`,
|
|
27
|
+
duration: Date.now() - start,
|
|
28
|
+
}];
|
|
29
|
+
}
|
|
30
|
+
let checks;
|
|
31
|
+
try {
|
|
32
|
+
checks = JSON.parse(renderResult.stdout);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [{
|
|
36
|
+
plugin: 'structural-smoke',
|
|
37
|
+
test: 'render-check',
|
|
38
|
+
status: 'fail',
|
|
39
|
+
severity: 'critical',
|
|
40
|
+
message: `Invalid JSON from render-check: ${renderResult.stdout.slice(0, 200)}`,
|
|
41
|
+
duration: Date.now() - start,
|
|
42
|
+
}];
|
|
43
|
+
}
|
|
44
|
+
for (const check of checks) {
|
|
45
|
+
const duration = Date.now() - start;
|
|
46
|
+
if (check.status === 'missing') {
|
|
47
|
+
results.push({
|
|
48
|
+
plugin: 'structural-smoke',
|
|
49
|
+
test: `create-render:${check.bundle}`,
|
|
50
|
+
status: 'fail',
|
|
51
|
+
severity: 'critical',
|
|
52
|
+
message: `No test node found for ${check.label} — generation may have failed`,
|
|
53
|
+
duration,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else if (check.status === 'error') {
|
|
57
|
+
results.push({
|
|
58
|
+
plugin: 'structural-smoke',
|
|
59
|
+
test: `create-render:${check.bundle}`,
|
|
60
|
+
status: 'fail',
|
|
61
|
+
severity: 'critical',
|
|
62
|
+
message: `Render error for ${check.label} (nid ${check.nid}): ${check.error}`,
|
|
63
|
+
duration,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else if (check.html_length !== undefined && check.html_length < 50) {
|
|
67
|
+
results.push({
|
|
68
|
+
plugin: 'structural-smoke',
|
|
69
|
+
test: `create-render:${check.bundle}`,
|
|
70
|
+
status: 'warn',
|
|
71
|
+
severity: 'minor',
|
|
72
|
+
message: `${check.label} rendered but output suspiciously short (${check.html_length} bytes)`,
|
|
73
|
+
duration,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
results.push({
|
|
78
|
+
plugin: 'structural-smoke',
|
|
79
|
+
test: `create-render:${check.bundle}`,
|
|
80
|
+
status: 'pass',
|
|
81
|
+
severity: 'info',
|
|
82
|
+
message: `${check.label}: created and rendered (${check.html_length} bytes)`,
|
|
83
|
+
duration,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return results;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=structural-smoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structural-smoke.js","sourceRoot":"","sources":["../../src/plugins/structural-smoke.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
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 type { TestPlugin } from '../types.js';
|
|
8
|
+
export declare const visualRegressionPlugin: TestPlugin;
|