@testsmith/testblocks 0.9.5 → 0.9.8
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/dist/cli/index.js +4 -1
- package/dist/client/assets/index-CDAzk2fI.css +1 -0
- package/dist/client/assets/index-CYoJ_6bl.js +2231 -0
- package/dist/client/assets/index-CYoJ_6bl.js.map +1 -0
- package/dist/client/index.html +22 -2
- package/dist/core/blocks/api/ApiDeleteBlock.js +1 -1
- package/dist/core/blocks/api/ApiGetBlock.js +1 -1
- package/dist/core/blocks/api/ApiPatchBlock.js +2 -1
- package/dist/core/blocks/api/ApiPostBlock.js +2 -1
- package/dist/core/blocks/api/ApiPutBlock.js +2 -1
- package/dist/server/executor.d.ts +14 -0
- package/dist/server/executor.js +27 -7
- package/dist/server/index.js +117 -11
- package/dist/server/openApiParser.d.ts +82 -0
- package/dist/server/openApiParser.js +495 -0
- package/dist/server/startServer.d.ts +1 -0
- package/dist/server/startServer.js +216 -6
- package/package.json +4 -2
- package/dist/client/assets/index-BzcQ5WS6.css +0 -1
- package/dist/client/assets/index-CJ8vFqNf.js +0 -2197
- package/dist/client/assets/index-CJ8vFqNf.js.map +0 -1
|
@@ -140,6 +140,7 @@ async function startServer(options = {}) {
|
|
|
140
140
|
// Set directories
|
|
141
141
|
const pluginsDir = options.pluginsDir || path_1.default.join(workingDir, 'plugins');
|
|
142
142
|
const globalsDir = options.globalsDir || workingDir;
|
|
143
|
+
const projectDir = options.projectDir ? path_1.default.resolve(options.projectDir) : null;
|
|
143
144
|
(0, plugins_1.setPluginsDirectory)(pluginsDir);
|
|
144
145
|
(0, globals_1.setGlobalsDirectory)(globalsDir);
|
|
145
146
|
// Load plugins and globals
|
|
@@ -167,6 +168,166 @@ async function startServer(options = {}) {
|
|
|
167
168
|
app.get('/api/version', (_req, res) => {
|
|
168
169
|
res.json({ version: VERSION });
|
|
169
170
|
});
|
|
171
|
+
// Initial project directory (for auto-open)
|
|
172
|
+
app.get('/api/initial-project', (_req, res) => {
|
|
173
|
+
res.json({ projectDir });
|
|
174
|
+
});
|
|
175
|
+
// Server-side file system access (when projectDir is specified)
|
|
176
|
+
// List files in project directory
|
|
177
|
+
app.get('/api/project/files', async (_req, res) => {
|
|
178
|
+
if (!projectDir) {
|
|
179
|
+
return res.status(400).json({ error: 'No project directory configured' });
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const scanDir = async (dirPath, relativePath = '') => {
|
|
183
|
+
const entries = await fs_1.default.promises.readdir(dirPath, { withFileTypes: true });
|
|
184
|
+
const items = [];
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
// Skip hidden files and common non-project directories
|
|
187
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const entryRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
191
|
+
const fullPath = path_1.default.join(dirPath, entry.name);
|
|
192
|
+
if (entry.isDirectory()) {
|
|
193
|
+
const children = await scanDir(fullPath, entryRelativePath);
|
|
194
|
+
items.push({
|
|
195
|
+
name: entry.name,
|
|
196
|
+
path: entryRelativePath,
|
|
197
|
+
type: 'folder',
|
|
198
|
+
children,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
else if (entry.name.endsWith('.testblocks.json') || entry.name === 'globals.json') {
|
|
202
|
+
items.push({
|
|
203
|
+
name: entry.name,
|
|
204
|
+
path: entryRelativePath,
|
|
205
|
+
type: 'file',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Sort: folders first, then alphabetically
|
|
210
|
+
items.sort((a, b) => {
|
|
211
|
+
const aObj = a;
|
|
212
|
+
const bObj = b;
|
|
213
|
+
if (aObj.type !== bObj.type) {
|
|
214
|
+
return aObj.type === 'folder' ? -1 : 1;
|
|
215
|
+
}
|
|
216
|
+
return aObj.name.localeCompare(bObj.name);
|
|
217
|
+
});
|
|
218
|
+
return items;
|
|
219
|
+
};
|
|
220
|
+
const files = await scanDir(projectDir);
|
|
221
|
+
res.json({
|
|
222
|
+
projectDir,
|
|
223
|
+
name: path_1.default.basename(projectDir),
|
|
224
|
+
files,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
res.status(500).json({ error: error.message });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
// Read a file from project directory
|
|
232
|
+
app.get('/api/project/read', async (req, res) => {
|
|
233
|
+
if (!projectDir) {
|
|
234
|
+
return res.status(400).json({ error: 'No project directory configured' });
|
|
235
|
+
}
|
|
236
|
+
const filePath = req.query.path;
|
|
237
|
+
if (!filePath) {
|
|
238
|
+
return res.status(400).json({ error: 'File path is required' });
|
|
239
|
+
}
|
|
240
|
+
// Security: ensure path doesn't escape project directory
|
|
241
|
+
const fullPath = path_1.default.resolve(projectDir, filePath);
|
|
242
|
+
if (!fullPath.startsWith(projectDir)) {
|
|
243
|
+
return res.status(403).json({ error: 'Access denied' });
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const content = await fs_1.default.promises.readFile(fullPath, 'utf-8');
|
|
247
|
+
res.json({ content: JSON.parse(content) });
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
res.status(500).json({ error: error.message });
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
// Write a file to project directory
|
|
254
|
+
app.post('/api/project/write', async (req, res) => {
|
|
255
|
+
if (!projectDir) {
|
|
256
|
+
return res.status(400).json({ error: 'No project directory configured' });
|
|
257
|
+
}
|
|
258
|
+
const { path: filePath, content } = req.body;
|
|
259
|
+
if (!filePath) {
|
|
260
|
+
return res.status(400).json({ error: 'File path is required' });
|
|
261
|
+
}
|
|
262
|
+
// Security: ensure path doesn't escape project directory
|
|
263
|
+
const fullPath = path_1.default.resolve(projectDir, filePath);
|
|
264
|
+
if (!fullPath.startsWith(projectDir)) {
|
|
265
|
+
return res.status(403).json({ error: 'Access denied' });
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
// Ensure parent directory exists
|
|
269
|
+
await fs_1.default.promises.mkdir(path_1.default.dirname(fullPath), { recursive: true });
|
|
270
|
+
await fs_1.default.promises.writeFile(fullPath, JSON.stringify(content, null, 2));
|
|
271
|
+
res.json({ success: true });
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
res.status(500).json({ error: error.message });
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
// Create a new file in project directory
|
|
278
|
+
app.post('/api/project/create', async (req, res) => {
|
|
279
|
+
if (!projectDir) {
|
|
280
|
+
return res.status(400).json({ error: 'No project directory configured' });
|
|
281
|
+
}
|
|
282
|
+
const { path: filePath, content } = req.body;
|
|
283
|
+
if (!filePath) {
|
|
284
|
+
return res.status(400).json({ error: 'File path is required' });
|
|
285
|
+
}
|
|
286
|
+
// Security: ensure path doesn't escape project directory
|
|
287
|
+
const fullPath = path_1.default.resolve(projectDir, filePath);
|
|
288
|
+
if (!fullPath.startsWith(projectDir)) {
|
|
289
|
+
return res.status(403).json({ error: 'Access denied' });
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
// Check if file already exists
|
|
293
|
+
try {
|
|
294
|
+
await fs_1.default.promises.access(fullPath);
|
|
295
|
+
return res.status(409).json({ error: 'File already exists' });
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// File doesn't exist, good
|
|
299
|
+
}
|
|
300
|
+
// Ensure parent directory exists
|
|
301
|
+
await fs_1.default.promises.mkdir(path_1.default.dirname(fullPath), { recursive: true });
|
|
302
|
+
await fs_1.default.promises.writeFile(fullPath, JSON.stringify(content, null, 2));
|
|
303
|
+
res.json({ success: true });
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
res.status(500).json({ error: error.message });
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
// Delete a file from project directory
|
|
310
|
+
app.delete('/api/project/delete', async (req, res) => {
|
|
311
|
+
if (!projectDir) {
|
|
312
|
+
return res.status(400).json({ error: 'No project directory configured' });
|
|
313
|
+
}
|
|
314
|
+
const filePath = req.query.path;
|
|
315
|
+
if (!filePath) {
|
|
316
|
+
return res.status(400).json({ error: 'File path is required' });
|
|
317
|
+
}
|
|
318
|
+
// Security: ensure path doesn't escape project directory
|
|
319
|
+
const fullPath = path_1.default.resolve(projectDir, filePath);
|
|
320
|
+
if (!fullPath.startsWith(projectDir)) {
|
|
321
|
+
return res.status(403).json({ error: 'Access denied' });
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
await fs_1.default.promises.unlink(fullPath);
|
|
325
|
+
res.json({ success: true });
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
res.status(500).json({ error: error.message });
|
|
329
|
+
}
|
|
330
|
+
});
|
|
170
331
|
// List available plugins (with full block definitions for client registration)
|
|
171
332
|
app.get('/api/plugins', (_req, res) => {
|
|
172
333
|
const available = (0, plugins_1.discoverPlugins)();
|
|
@@ -242,14 +403,37 @@ async function startServer(options = {}) {
|
|
|
242
403
|
const globalVars = (0, globals_1.getGlobalVariables)();
|
|
243
404
|
const globalProcs = (0, globals_1.getGlobalProcedures)();
|
|
244
405
|
const testIdAttr = (0, globals_1.getTestIdAttribute)();
|
|
245
|
-
const
|
|
406
|
+
const executorOpts = {
|
|
246
407
|
headless: req.query.headless !== 'false',
|
|
247
408
|
timeout: Number(req.query.timeout) || (0, globals_1.getGlobalTimeout)(),
|
|
248
409
|
variables: globalVars,
|
|
249
410
|
procedures: globalProcs,
|
|
250
|
-
testIdAttribute: testIdAttr,
|
|
411
|
+
testIdAttribute: req.query.testIdAttribute || testIdAttr,
|
|
251
412
|
baseDir: globalsDir,
|
|
252
|
-
}
|
|
413
|
+
};
|
|
414
|
+
if (req.query.locale)
|
|
415
|
+
executorOpts.locale = req.query.locale;
|
|
416
|
+
if (req.query.timezoneId)
|
|
417
|
+
executorOpts.timezoneId = req.query.timezoneId;
|
|
418
|
+
if (req.query.geoLatitude && req.query.geoLongitude) {
|
|
419
|
+
executorOpts.geolocation = {
|
|
420
|
+
latitude: parseFloat(req.query.geoLatitude),
|
|
421
|
+
longitude: parseFloat(req.query.geoLongitude),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
if (req.query.viewportWidth && req.query.viewportHeight) {
|
|
425
|
+
executorOpts.viewport = {
|
|
426
|
+
width: parseInt(req.query.viewportWidth, 10),
|
|
427
|
+
height: parseInt(req.query.viewportHeight, 10),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
if (req.query.localStorage) {
|
|
431
|
+
try {
|
|
432
|
+
executorOpts.localStorage = JSON.parse(req.query.localStorage);
|
|
433
|
+
}
|
|
434
|
+
catch { /* ignore invalid JSON */ }
|
|
435
|
+
}
|
|
436
|
+
const executor = new executor_1.TestExecutor(executorOpts);
|
|
253
437
|
const results = await executor.runTestFile(mergedTestFile);
|
|
254
438
|
const passed = results.filter(r => r.status === 'passed').length;
|
|
255
439
|
const failed = results.filter(r => r.status === 'failed').length;
|
|
@@ -292,14 +476,37 @@ async function startServer(options = {}) {
|
|
|
292
476
|
const globalVars = (0, globals_1.getGlobalVariables)();
|
|
293
477
|
const globalProcs = (0, globals_1.getGlobalProcedures)();
|
|
294
478
|
const testIdAttr = (0, globals_1.getTestIdAttribute)();
|
|
295
|
-
const
|
|
479
|
+
const executorOpts = {
|
|
296
480
|
headless: req.query.headless !== 'false',
|
|
297
481
|
timeout: Number(req.query.timeout) || (0, globals_1.getGlobalTimeout)(),
|
|
298
482
|
variables: globalVars,
|
|
299
483
|
procedures: globalProcs,
|
|
300
|
-
testIdAttribute: testIdAttr,
|
|
484
|
+
testIdAttribute: req.query.testIdAttribute || testIdAttr,
|
|
301
485
|
baseDir: globalsDir,
|
|
302
|
-
}
|
|
486
|
+
};
|
|
487
|
+
if (req.query.locale)
|
|
488
|
+
executorOpts.locale = req.query.locale;
|
|
489
|
+
if (req.query.timezoneId)
|
|
490
|
+
executorOpts.timezoneId = req.query.timezoneId;
|
|
491
|
+
if (req.query.geoLatitude && req.query.geoLongitude) {
|
|
492
|
+
executorOpts.geolocation = {
|
|
493
|
+
latitude: parseFloat(req.query.geoLatitude),
|
|
494
|
+
longitude: parseFloat(req.query.geoLongitude),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
if (req.query.viewportWidth && req.query.viewportHeight) {
|
|
498
|
+
executorOpts.viewport = {
|
|
499
|
+
width: parseInt(req.query.viewportWidth, 10),
|
|
500
|
+
height: parseInt(req.query.viewportHeight, 10),
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (req.query.localStorage) {
|
|
504
|
+
try {
|
|
505
|
+
executorOpts.localStorage = JSON.parse(req.query.localStorage);
|
|
506
|
+
}
|
|
507
|
+
catch { /* ignore invalid JSON */ }
|
|
508
|
+
}
|
|
509
|
+
const executor = new executor_1.TestExecutor(executorOpts);
|
|
303
510
|
// Register file-level procedures (overrides globals)
|
|
304
511
|
if (mergedTestFile.procedures) {
|
|
305
512
|
executor.registerProcedures(mergedTestFile.procedures);
|
|
@@ -517,6 +724,9 @@ async function startServer(options = {}) {
|
|
|
517
724
|
console.log(` Working directory: ${workingDir}`);
|
|
518
725
|
console.log(` Plugins: ${pluginsDir}`);
|
|
519
726
|
console.log(` Globals: ${globalsDir}`);
|
|
727
|
+
if (projectDir) {
|
|
728
|
+
console.log(` Project (auto-open): ${projectDir}`);
|
|
729
|
+
}
|
|
520
730
|
console.log('\nPress Ctrl+C to stop\n');
|
|
521
731
|
// Open browser if requested
|
|
522
732
|
if (options.open) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testsmith/testblocks",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"description": "Visual test automation tool with Blockly - API and Playwright testing",
|
|
5
5
|
"author": "Roy de Kleijn",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"test:coverage": "vitest run --coverage"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
+
"@apidevtools/swagger-parser": "^12.1.0",
|
|
65
66
|
"@blockly/field-multilineinput": "^6.0.5",
|
|
66
67
|
"@faker-js/faker": "^10.1.0",
|
|
67
68
|
"@playwright/test": "^1.57.0",
|
|
@@ -77,7 +78,8 @@
|
|
|
77
78
|
"react": "^18.3.1",
|
|
78
79
|
"react-dom": "^18.3.1",
|
|
79
80
|
"xmldom": "^0.6.0",
|
|
80
|
-
"xpath": "^0.0.34"
|
|
81
|
+
"xpath": "^0.0.34",
|
|
82
|
+
"yaml": "^2.8.2"
|
|
81
83
|
},
|
|
82
84
|
"devDependencies": {
|
|
83
85
|
"@eslint/js": "^9.39.2",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--ts-blue: #205d96;--ts-blue-light: #e0f2fe;--ts-blue-dark: #1a4a78;--ts-green: #9fc93c;--ts-green-light: #ecfccb;--ts-green-dark: #5c7a1f;--ts-gray: #59575d;--ts-gray-light: #6b7280;--ts-gray-bg: #f8fafc;--primary-color: var(--ts-blue);--primary-dark: var(--ts-blue-dark);--primary-light: var(--ts-blue-light);--secondary-color: var(--ts-gray);--background: var(--ts-gray-bg);--surface: #ffffff;--error: #d32f2f;--success: var(--ts-green-dark);--success-light: var(--ts-green-light);--warning: #e67e00;--text-primary: var(--ts-gray);--text-secondary: var(--ts-gray-light);--border-color: #e0e0e0}*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;background-color:var(--background);color:var(--text-primary)}.app{display:flex;flex-direction:column;height:100vh;overflow:hidden}.header{display:flex;align-items:center;justify-content:space-between;padding:0 16px;height:56px;background:var(--primary-color);color:#fff;box-shadow:0 2px 4px #0000001a;z-index:100}.header h1{font-size:20px;font-weight:500;display:flex;align-items:center;gap:8px}.header-actions{display:flex;gap:8px}.btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,opacity .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-primary{background:#fff;color:var(--primary-color)}.btn-primary:hover:not(:disabled){background:#ffffffe6}.btn-secondary{background:#ffffff26;color:#fff}.btn-secondary:hover:not(:disabled){background:#ffffff40}.btn-success{background:var(--ts-green);color:#fff}.btn-success:hover:not(:disabled){background:var(--ts-green-dark)}.btn-danger{background:var(--error);color:#fff}.btn-danger:hover:not(:disabled){background:#c62828}.main-content{display:flex;flex:1;overflow:hidden}.sidebar{width:280px;background:var(--surface);border-right:1px solid var(--border-color);display:flex;flex-direction:column;overflow:hidden;transition:width .2s ease}.sidebar.collapsed{width:40px}.sidebar-toggle-header{display:flex;align-items:center;justify-content:flex-end;padding:8px;border-bottom:1px solid var(--border-color)}.sidebar.collapsed .sidebar-toggle-header{justify-content:center}.sidebar-header{padding:16px;border-bottom:1px solid var(--border-color)}.sidebar-header h2{font-size:14px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px}.test-list{flex:1;overflow-y:auto;padding:8px}.test-item{padding:12px;border-radius:4px;cursor:pointer;border:1px solid transparent;margin-bottom:4px;transition:background-color .2s}.test-item:hover{background:var(--background)}.test-item.active{background:#1976d214;border-color:var(--primary-color)}.test-item-name{font-weight:500;font-size:14px;margin-bottom:4px}.test-item-steps{font-size:12px;color:var(--text-secondary)}.add-test-btn{margin:8px;padding:12px;border:2px dashed var(--border-color);border-radius:4px;background:transparent;color:var(--text-secondary);cursor:pointer;font-size:14px;transition:border-color .2s,color .2s}.add-test-btn:hover{border-color:var(--primary-color);color:var(--primary-color)}.editor-area{flex:1;display:flex;flex-direction:column;overflow:hidden}.editor-toolbar{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:var(--surface);border-bottom:1px solid var(--border-color)}.test-name-input{font-size:16px;font-weight:500;padding:8px 12px;border:1px solid var(--border-color);border-radius:4px;width:300px}.test-name-input:focus{outline:none;border-color:var(--primary-color)}.blockly-container{flex:1;position:relative}#blockly-div{position:absolute;top:0;left:0;right:0;bottom:0}.blocklyTreeSeparator{height:1px!important;margin:12px 8px!important;background-color:#0006!important;border:none!important}.blocklyWidgetDiv .blocklyHtmlInput{max-width:350px;font-family:monospace;font-size:12px;line-height:1.4}.results-panel{width:350px;background:var(--surface);border-left:1px solid var(--border-color);display:flex;flex-direction:column;overflow:hidden;transition:width .2s ease}.results-panel.collapsed{width:40px}.results-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border-color);gap:12px}.results-panel.collapsed .results-header{justify-content:center;padding:12px 8px}.panel-toggle-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:12px;padding:4px 8px;border-radius:4px;flex-shrink:0}.panel-toggle-btn:hover{background:var(--hover-bg);color:var(--text-primary)}.results-header h2{font-size:14px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;flex:1;display:flex;align-items:center;gap:8px}.results-summary{display:flex;gap:12px;font-size:13px;font-weight:500;padding:10px 16px;background:var(--bg-tertiary);border-bottom:1px solid var(--border-color)}.results-summary .passed-count{color:var(--success)}.results-summary .failed-count{color:var(--danger)}.results-summary .skipped-count{color:#f59e0b}.results-actions{display:flex;gap:8px}.btn-report{padding:4px 8px;font-size:11px;font-weight:500;border:1px solid var(--border-color);border-radius:4px;background:var(--surface);color:var(--text-secondary);cursor:pointer;transition:all .2s}.btn-report:hover{background:var(--primary-light);border-color:var(--primary-color);color:var(--primary-color)}.results-content{flex:1;overflow-y:auto;padding:16px}.result-item{padding:12px;border-radius:4px;margin-bottom:8px;border-left:4px solid}.result-item.passed{background:#388e3c14;border-left-color:var(--success)}.result-item.failed{background:#d32f2f14;border-left-color:var(--error)}.result-item.running{background:#1976d214;border-left-color:var(--primary-color)}.result-step{font-size:13px;margin-bottom:4px}.result-error{font-size:12px;color:var(--error);font-family:monospace;white-space:pre-wrap;margin-top:8px}.result-duration{font-size:11px;color:var(--text-secondary)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.modal{background:var(--surface);border-radius:8px;padding:24px;min-width:400px;max-width:600px;max-height:80vh;overflow-y:auto;box-shadow:0 4px 20px #00000026}.modal h2{margin-bottom:16px}.modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:24px}.folder-hooks-modal{background:var(--surface);border-radius:8px;width:90vw;max-width:1200px;height:80vh;display:flex;flex-direction:column;box-shadow:0 4px 20px #00000026}.folder-hooks-header{padding:16px 24px;border-bottom:1px solid var(--border-color);position:relative}.folder-hooks-header h2{margin:0 0 4px;font-size:18px}.folder-hooks-description{font-size:13px;color:var(--text-secondary);margin:0}.folder-hooks-tabs{display:flex;border-bottom:1px solid var(--border-color);padding:0 16px;background:var(--background)}.folder-hooks-tab{padding:12px 16px;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent;display:flex;align-items:center;gap:8px}.folder-hooks-tab:hover{color:var(--text-primary)}.folder-hooks-tab.active{color:var(--primary-color);border-bottom-color:var(--primary-color)}.folder-hooks-tab .tab-badge{font-size:10px;padding:2px 6px;border-radius:10px;background:var(--primary-color);color:#fff}.folder-hooks-workspace{flex:1;min-height:0}.folder-hooks-footer{padding:16px 24px;border-top:1px solid var(--border-color);display:flex;justify-content:flex-end;gap:8px}.form-group{margin-bottom:16px}.form-group label{display:block;font-size:14px;font-weight:500;margin-bottom:6px}.form-group input,.form-group textarea{width:100%;padding:10px 12px;border:1px solid var(--border-color);border-radius:4px;font-size:14px}.form-group input:focus,.form-group textarea:focus{outline:none;border-color:var(--primary-color)}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-secondary);text-align:center;padding:40px}.empty-state h3{margin-bottom:8px;color:var(--text-primary)}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--background)}::-webkit-scrollbar-thumb{background:#bdbdbd;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#9e9e9e}.status-indicator{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px}.status-indicator.running{background:var(--primary-color);animation:pulse 1s infinite}.status-indicator.passed{background:var(--success)}.status-indicator.failed{background:var(--error)}.sidebar-section{border-bottom:1px solid var(--border-color)}.sidebar-section:last-child{border-bottom:none;flex:1;display:flex;flex-direction:column;overflow:hidden}.sidebar-header.clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.sidebar-header.clickable:hover{background:var(--background)}.variables-list{padding:8px}.variable-item{display:flex;align-items:center;gap:8px;padding:6px 0}.variable-name{font-family:monospace;font-size:12px;color:var(--primary-color);min-width:80px;flex-shrink:0}.variable-value{flex:1;padding:4px 8px;border:1px solid var(--border-color);border-radius:4px;font-size:12px}.variable-value:focus{outline:none;border-color:var(--primary-color)}.global-variables{background:linear-gradient(to right,rgba(32,93,150,.05),transparent)}.variable-item.global{padding:4px 0}.variable-item.global .variable-name{color:var(--ts-blue);font-size:11px;word-break:break-all}.variable-value.readonly{background:var(--background);color:var(--text-secondary);font-family:monospace;font-size:11px;padding:2px 6px;border:none;border-radius:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.variable-value.global-input{flex:1;padding:4px 6px;border:1px solid rgba(32,93,150,.3);border-radius:4px;font-size:11px;font-family:monospace;background:#205d960d;color:var(--text-primary)}.variable-value.global-input:focus{outline:none;border-color:var(--ts-blue);background:#205d961a}.variable-group{margin-bottom:8px}.variable-group-header{font-weight:600;font-size:11px;color:var(--text-secondary);text-transform:uppercase;padding:4px 0;border-bottom:1px solid var(--border-color);margin-bottom:4px}.variable-group-content{padding-left:12px;border-left:2px solid rgba(32,93,150,.3)}.global-badge{font-size:12px;margin-left:8px;opacity:.7}.btn-icon{background:none;border:none;color:var(--text-secondary);cursor:pointer;padding:4px 8px;font-size:16px;line-height:1;border-radius:4px}.btn-icon:hover{background:var(--background);color:var(--error)}.add-variable-btn{width:100%;margin-top:8px;padding:8px;border:1px dashed var(--border-color);border-radius:4px;background:transparent;color:var(--text-secondary);cursor:pointer;font-size:12px}.add-variable-btn:hover{border-color:var(--primary-color);color:var(--primary-color)}.test-item{display:flex;align-items:center;justify-content:space-between}.test-item-content{flex:1;min-width:0}.test-item-name{display:flex;align-items:center;gap:6px}.data-driven-badge{font-size:10px;font-weight:600;background:#1565c0;color:#fff;padding:1px 5px;border-radius:8px;margin-left:4px}.btn-run-test{background:none;border:1px solid var(--border-color);border-radius:4px;padding:4px 8px;cursor:pointer;font-size:10px;color:var(--text-secondary);flex-shrink:0;margin-left:8px}.btn-run-test:hover:not(:disabled){background:var(--primary-color);border-color:var(--primary-color);color:#fff}.btn-run-test:disabled{opacity:.5;cursor:not-allowed}.status-dot{display:inline-block;width:8px;height:8px;border-radius:50%;flex-shrink:0}.status-dot.passed{background:var(--success)}.status-dot.failed{background:var(--error)}.test-item.passed{background:#388e3c0d}.test-item.failed{background:#d32f2f0d}.test-item.passed.active{background:#388e3c1f}.test-item.failed.active{background:#d32f2f1f}.test-item.disabled{opacity:.6;background:#9e9e9e0d}.test-item.disabled.active{background:#9e9e9e1f}.test-item.disabled .btn-run-test{opacity:.4;cursor:not-allowed}.test-name-disabled{text-decoration:line-through;color:var(--text-secondary)}.status-dot.skipped{background:#f59e0b}.test-item.skipped{background:#f59e0b0d}.test-item.skipped.active{background:#f59e0b1f}.test-id-indicator{display:flex;align-items:center;gap:6px;color:#ffffffe6;font-size:13px;padding:6px 12px;border-radius:4px;background:#ffffff1a}.test-id-indicator code{font-family:Monaco,Menlo,monospace;background:#fff3;padding:2px 6px;border-radius:3px;font-size:12px}.headless-toggle{display:flex;align-items:center;gap:6px;color:#fff;font-size:14px;cursor:pointer;padding:8px 12px;border-radius:4px;background:#ffffff1a}.headless-toggle:hover{background:#fff3}.headless-toggle input[type=checkbox]{width:16px;height:16px;cursor:pointer}.create-block-modal{min-width:500px;max-width:600px}.create-block-modal h2{color:var(--text-primary);margin-bottom:20px}.helper-text{font-size:12px;color:var(--text-secondary);margin-bottom:8px}.color-picker{display:flex;gap:8px}.color-option{width:32px;height:32px;border-radius:4px;border:2px solid transparent;cursor:pointer;transition:transform .1s,border-color .1s}.color-option:hover{transform:scale(1.1)}.color-option.selected{border-color:var(--text-primary)}.param-list{max-height:200px;overflow-y:auto;border:1px solid var(--border-color);border-radius:4px;padding:8px}.empty-params{color:var(--text-secondary);font-size:13px;padding:12px;text-align:center}.param-item{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;cursor:pointer;font-size:13px}.param-item:hover{background:var(--background)}.param-item input[type=checkbox]{flex-shrink:0}.param-name{font-weight:500;color:var(--primary-color)}.param-source{color:var(--text-secondary);font-size:11px}.param-default{color:var(--text-secondary);font-size:11px;margin-left:auto;font-family:monospace}.steps-preview{max-height:150px;overflow-y:auto;border:1px solid var(--border-color);border-radius:4px;padding:8px}.step-preview-item{display:flex;align-items:center;gap:8px;padding:4px 8px;font-size:13px}.step-number{color:var(--text-secondary);font-size:11px;width:20px}.step-type{color:var(--text-primary)}.create-block-modal .btn-primary{background:var(--primary-color);color:#fff}.create-block-modal .btn-primary:hover{background:var(--primary-dark)}.create-block-modal .btn-secondary{background:var(--background);color:var(--text-primary)}.result-test-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.result-file-name{color:var(--text-secondary);font-size:12px;margin-right:4px}.result-test-name{font-weight:500;flex:1}.result-steps{margin-top:8px;border-top:1px solid var(--border-color);padding-top:8px}.step-result-item{margin-bottom:6px;border-radius:4px;background:#00000005}.step-result-item.passed{border-left:3px solid var(--success)}.step-result-item.failed{border-left:3px solid var(--error)}.step-result-header{display:flex;align-items:center;gap:8px;padding:6px 8px;cursor:pointer;-webkit-user-select:none;user-select:none}.step-result-header:hover{background:#0000000a}.step-result-type{font-size:12px;color:var(--text-primary);white-space:nowrap}.step-result-summary{flex:1;font-size:11px;color:var(--text-secondary);font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:300px}.step-result-duration{font-size:11px;color:var(--text-secondary);white-space:nowrap}.step-result-expand{font-size:10px;color:var(--text-secondary);width:16px;text-align:center}.step-result-header.expandable{cursor:pointer}.step-result-header.expandable:hover{background:#0000000a}.step-result-details{padding:8px;background:var(--background);border-radius:0 0 4px 4px}.step-output{background:var(--surface);border-radius:4px;padding:8px}.step-result-error{padding:6px 8px;font-size:11px;color:var(--error);background:#d32f2f0d;font-family:monospace}.step-screenshot{margin-bottom:12px}.screenshot-label{font-size:11px;font-weight:600;color:var(--text-secondary);margin-bottom:6px;text-transform:uppercase;letter-spacing:.5px}.failure-screenshot{max-width:100%;max-height:300px;border:2px solid var(--error);border-radius:6px;cursor:pointer;transition:transform .2s,box-shadow .2s;display:block}.failure-screenshot:hover{transform:scale(1.02);box-shadow:0 4px 12px #d32f2f4d}.stack-trace{font-size:10px;line-height:1.4;color:var(--error);max-height:200px;overflow-y:auto}.step-result-response{padding:8px;background:var(--background);border-radius:0 0 4px 4px}.response-status{display:flex;align-items:center;gap:8px;margin-bottom:8px}.response-label{font-size:11px;color:var(--text-secondary)}.response-status-code{font-family:monospace;font-size:12px;font-weight:600;padding:2px 6px;border-radius:3px;background:var(--surface)}.response-status-code.status-success{color:var(--success);background:#388e3c1a}.response-status-code.status-client-error{color:var(--warning);background:#f57c001a}.response-status-code.status-server-error{color:var(--error);background:#d32f2f1a}.response-section{margin-top:8px}.response-section summary{font-size:11px;color:var(--text-secondary);cursor:pointer;padding:4px 0;-webkit-user-select:none;user-select:none}.response-section summary:hover{color:var(--primary-color)}.response-pre{margin:4px 0 0;padding:8px;background:var(--surface);border:1px solid var(--border-color);border-radius:4px;font-size:11px;font-family:monospace;overflow-x:auto;max-height:200px;overflow-y:auto;white-space:pre-wrap;word-break:break-all}.lifecycle-badge{display:inline-block;font-size:10px;font-weight:600;text-transform:uppercase;padding:2px 6px;border-radius:3px;background:var(--ts-gray);color:#fff;letter-spacing:.5px}.result-item.lifecycle,.result-item.lifecycle.passed{border-left-color:var(--ts-gray);background:#59575d14}.result-item.lifecycle.failed{border-left-color:#d32f2f;background:#d32f2f14}.editor-tabs{display:flex;background:var(--surface);border-bottom:1px solid var(--border-color);padding:0 8px}.editor-tab{padding:10px 16px;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent;transition:all .2s;display:flex;align-items:center;gap:6px}.editor-tab:hover{color:var(--text-primary);background:var(--background)}.editor-tab.active{color:var(--primary-color);border-bottom-color:var(--primary-color)}.editor-tab.test-tab{margin:0 auto 0 0;font-weight:600}.editor-tab.test-tab.active{color:var(--primary-color)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;font-size:11px;font-weight:600;background:var(--ts-gray);color:#fff;border-radius:9px}.editor-tab.active .tab-badge{background:var(--primary-color)}.lifecycle-toolbar-info{display:flex;align-items:center;gap:8px;color:var(--text-primary);font-size:14px;font-weight:500}.lifecycle-icon{font-size:16px}.lifecycle-hint{color:var(--text-secondary);font-weight:400;font-size:13px}.sidebar-tabs{display:flex;border-bottom:1px solid var(--border-color)}.sidebar-tab{flex:1;padding:10px;border:none;background:transparent;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent;transition:all .2s}.sidebar-tab:hover{color:var(--text-primary);background:var(--background)}.sidebar-tab.active{color:var(--primary-color);border-bottom-color:var(--primary-color)}.file-tree-section,.file-tree{flex:1;overflow:hidden;display:flex;flex-direction:column}.file-tree-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--border-color);background:var(--background)}.file-tree-root-name{font-weight:600;font-size:13px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-tree-refresh{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:16px;padding:4px;border-radius:4px}.file-tree-refresh:hover{background:var(--surface);color:var(--primary-color)}.file-tree-header-actions{display:flex;align-items:center;gap:4px}.file-tree-action-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:12px;padding:4px 6px;border-radius:4px}.file-tree-action-btn:hover{background:var(--surface);color:var(--primary-color)}.folder-actions{position:relative;margin-left:auto}.folder-action-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:14px;font-weight:700;width:20px;height:20px;border-radius:4px;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s}.file-tree-item:hover .folder-action-btn{opacity:1}.folder-action-btn:hover{background:var(--primary-light);color:var(--primary-color)}.folder-actions-menu{position:absolute;top:100%;right:0;background:#fff;border:1px solid var(--border-color);border-radius:6px;box-shadow:0 4px 12px #00000026;z-index:100;min-width:140px;padding:4px 0}.folder-actions-menu button{display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:none;background:none;text-align:left;font-size:13px;color:var(--text-primary);cursor:pointer}.folder-actions-menu button:hover{background:var(--primary-light);color:var(--primary-color)}.folder-actions-menu button.delete-action:hover{background:#fee2e2;color:#dc2626}.folder-actions-menu button.run-action{color:#16a34a;font-weight:500}.folder-actions-menu button.run-action:hover{background:#dcfce7;color:#15803d}.folder-actions-menu button:disabled{opacity:.5;cursor:not-allowed}.file-tree-run-btn{color:#16a34a!important}.file-tree-run-btn:hover:not(:disabled){background:#dcfce7!important}.file-tree-run-btn:disabled{opacity:.5;cursor:not-allowed}.file-tree-content{flex:1;overflow-y:auto;padding:4px 0}.file-tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px 16px;color:var(--text-secondary);text-align:center}.file-tree-empty p{margin:4px 0;font-size:13px}.file-tree-hint{font-size:12px;opacity:.7}.file-tree-item{display:flex;align-items:center;gap:6px;padding:6px 8px;cursor:pointer;font-size:13px;color:var(--text-primary);border-left:2px solid transparent;transition:all .15s;position:relative}.file-tree-item:hover{background:var(--background)}.file-tree-item.selected{background:#1976d214;border-left-color:var(--primary-color)}.file-tree-item.folder{color:var(--text-secondary)}.file-tree-item[draggable=true]{cursor:grab}.file-tree-item[draggable=true]:active{cursor:grabbing}.file-tree-item.dragging{opacity:.5;background:var(--background)}.file-tree-item.drag-over{background:#1976d226;border-left-color:var(--primary-color);border-left-width:3px}.file-tree-item.drag-over:after{content:"";position:absolute;left:0;right:0;top:0;bottom:0;border:2px dashed var(--primary-color);border-radius:4px;pointer-events:none}.file-tree-icon{font-size:14px;flex-shrink:0}.file-tree-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.file-tree-badge{font-size:10px;font-weight:600;padding:2px 6px;border-radius:10px;background:var(--primary-color);color:#fff;flex-shrink:0}.file-tree-failed-indicator{color:#ef4444;font-size:10px;margin-left:4px;flex-shrink:0}.folder-hooks-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:12px;padding:2px 4px;opacity:0;transition:opacity .2s;flex-shrink:0}.file-tree-item.folder:hover .folder-hooks-btn{opacity:1}.folder-hooks-btn:hover{color:var(--primary-color)}.folder-hooks-indicator{font-size:10px;color:var(--primary-color);margin-left:4px;flex-shrink:0}.header-version{margin-left:8px;font-size:11px;font-weight:400;color:#ffffff80;background:#ffffff1a;padding:2px 6px;border-radius:4px}.header-file-path{margin-left:12px;font-size:12px;font-weight:400;color:#ffffffb3;max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-flex;align-items:center;gap:8px}.auto-save-indicator{font-size:11px;padding:2px 8px;border-radius:4px;flex-shrink:0}.auto-save-indicator.saving{background:#ffc10733;color:#ffc107;animation:pulse 1s ease-in-out infinite}.auto-save-indicator.saved{background:#4caf5033;color:#4caf50}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.folder-hooks-label{background:rgba(var(--ts-blue),.1);background:#e0f2fe;color:var(--ts-blue);padding:2px 8px;border-radius:4px;font-size:11px;font-weight:500;margin-right:6px}.help-modal{width:950px;max-width:95vw;height:85vh;max-height:85vh;display:flex;flex-direction:column;padding:0;overflow:hidden}.help-header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border-color);flex-shrink:0}.help-header h2{margin:0;font-size:20px;font-weight:500}.btn-close{background:none;border:none;font-size:24px;cursor:pointer;color:var(--text-secondary);padding:4px 8px;border-radius:4px;line-height:1}.btn-close:hover{background:var(--background);color:var(--text-primary)}.help-layout{display:flex;flex:1;overflow:hidden}.help-nav{width:200px;flex-shrink:0;background:var(--background);border-right:1px solid var(--border-color);padding:12px 0;overflow-y:auto}.help-nav-item{display:block;width:100%;padding:10px 20px;border:none;background:none;text-align:left;cursor:pointer;font-size:14px;color:var(--text-secondary);transition:background .2s,color .2s}.help-nav-item:hover{background:#0000000a;color:var(--text-primary)}.help-nav-item.active{background:#1976d21a;color:var(--primary-color);font-weight:500;border-left:3px solid var(--primary-color);padding-left:17px}.help-content{flex:1;overflow-y:auto;padding:24px 32px}.help-content h3{margin:0 0 8px;font-size:22px;font-weight:600;color:var(--text-primary)}.help-content>p{margin:0 0 24px;color:var(--text-secondary);font-size:15px;line-height:1.5}.help-feature{margin-bottom:24px;padding:16px 20px;background:var(--background);border-radius:8px}.help-feature h4{margin:0 0 12px;font-size:15px;font-weight:600;color:var(--text-primary)}.help-feature ol,.help-feature ul{margin:0;padding-left:20px}.help-feature li{margin-bottom:8px;line-height:1.5;color:var(--text-primary)}.help-feature li:last-child{margin-bottom:0}.help-feature p{margin:0 0 12px;line-height:1.5}.help-feature code{background:#0000000f;padding:2px 6px;border-radius:4px;font-family:SF Mono,Monaco,Courier New,monospace;font-size:13px;color:var(--primary-dark)}.help-tip{margin-bottom:24px;padding:14px 18px;background:#1976d214;border-left:3px solid var(--primary-color);border-radius:0 8px 8px 0;font-size:14px;line-height:1.5}.help-tip code{background:#0000000f;padding:2px 6px;border-radius:4px;font-family:SF Mono,Monaco,Courier New,monospace;font-size:13px}.help-code{margin:12px 0 0;padding:12px 16px;background:#1e1e1e;color:#d4d4d4;border-radius:6px;font-family:SF Mono,Monaco,Courier New,monospace;font-size:13px;line-height:1.5;overflow-x:auto;white-space:pre}.scan-project-btn{margin-top:8px}.scan-project-btn:disabled{opacity:.6;cursor:not-allowed}.matches-section{margin-top:12px}.matches-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;font-size:13px;color:var(--text-secondary)}.matches-actions{display:flex;gap:12px}.btn-link{background:none;border:none;color:var(--primary-color);cursor:pointer;font-size:12px;padding:0}.btn-link:hover{text-decoration:underline}.matches-list{max-height:200px;overflow-y:auto;border:1px solid var(--border-color);border-radius:6px;background:var(--surface)}.match-file-group{border-bottom:1px solid var(--border-color)}.match-file-group:last-child{border-bottom:none}.match-file-name{font-size:12px;font-weight:600;color:var(--text-primary);padding:8px 12px 4px;background:var(--background)}.match-item{display:flex;align-items:center;gap:8px;padding:6px 12px;cursor:pointer;transition:background .15s}.match-item:hover{background:#00000008}.match-item input[type=checkbox]{flex-shrink:0}.match-location{font-size:13px;color:var(--text-primary);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.match-detail{font-size:12px;color:var(--text-secondary);margin-left:4px}.record-dialog{min-width:500px;max-width:600px}.record-dialog .modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid var(--border-color)}.record-dialog .modal-header h2{margin:0;font-size:18px}.record-dialog .modal-body{min-height:150px}.record-url-input{display:flex;flex-direction:column;gap:12px}.record-url-input p{margin:0;color:var(--text-primary)}.url-input{width:100%;padding:10px 12px;border:1px solid var(--border-color);border-radius:4px;font-size:14px}.url-input:focus{outline:none;border-color:var(--primary-color)}.record-hint{font-size:13px;color:var(--text-secondary);margin-top:4px}.record-status{display:flex;align-items:center;gap:16px;padding:20px;background:var(--background);border-radius:8px;margin:16px 0}.record-status.recording{border-left:4px solid var(--error);background:#d32f2f0d}.record-status.processing{border-left:4px solid var(--warning);background:#f57c000d;justify-content:center}.recording-indicator{width:16px;height:16px;background:var(--error);border-radius:50%;animation:pulse 1.5s ease-in-out infinite;flex-shrink:0}@keyframes pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(.9)}}.recording-info h3{margin:0 0 8px;font-size:16px;color:var(--text-primary)}.recording-info p{margin:0;font-size:13px;color:var(--text-secondary)}.processing-spinner{width:24px;height:24px;border:3px solid var(--border-color);border-top-color:var(--primary-color);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.record-preview h3{margin:0 0 12px;font-size:15px}.steps-preview{max-height:300px;overflow-y:auto;border:1px solid var(--border-color);border-radius:6px;background:var(--surface)}.step-preview-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--border-color);font-size:13px}.step-preview-item:last-child{border-bottom:none}.step-number{font-weight:600;color:var(--text-secondary);min-width:24px}.step-description{color:var(--text-primary);font-family:Monaco,Menlo,Ubuntu Mono,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.record-empty{text-align:center;padding:24px;color:var(--text-secondary)}.record-empty p{margin:0 0 8px}.record-error{color:var(--error);font-size:13px;padding:8px 12px;background:#d32f2f1a;border-radius:4px}.record-error-state{text-align:center;padding:20px}.record-error-state h3{margin:0 0 12px;color:var(--error)}.record-error-state .record-error{margin-bottom:16px}.btn-warning{background:var(--warning);color:#fff}.btn-warning:hover:not(:disabled){background:#e65100}.advanced-options{margin-top:16px;border-top:1px solid var(--border-color);padding-top:12px}.advanced-toggle{display:flex;align-items:center;gap:8px;background:none;border:none;padding:8px 0;font-size:13px;color:var(--text-secondary);cursor:pointer;width:100%;text-align:left}.advanced-toggle:hover{color:var(--text-primary)}.advanced-toggle .toggle-icon{font-size:10px;width:12px}.advanced-content{padding:12px 0 4px 20px}.option-row{display:flex;align-items:center;gap:12px;margin-bottom:8px}.option-row label{font-size:13px;color:var(--text-primary);white-space:nowrap;min-width:120px}.option-input{flex:1;padding:6px 10px;border:1px solid var(--border-color);border-radius:4px;font-size:13px;font-family:Monaco,Menlo,Ubuntu Mono,monospace}.option-input:focus{outline:none;border-color:var(--primary-color)}.option-hint{font-size:12px;color:var(--text-secondary);margin:4px 0 0}.json-editor-modal{width:600px;max-width:90vw;min-height:400px;max-height:80vh;display:flex;flex-direction:column;padding:0}.json-editor-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color)}.json-editor-header h3{margin:0;font-size:16px;font-weight:600}.json-editor-toolbar{display:flex;align-items:center;gap:8px;padding:12px 20px;background:var(--background);border-bottom:1px solid var(--border-color)}.json-editor-toolbar .btn-small{padding:4px 12px;font-size:12px}.json-status{margin-left:auto;font-size:12px;font-weight:500;padding:4px 8px;border-radius:4px}.json-status.valid{color:var(--success);background:var(--success-light)}.json-status.invalid{color:var(--error);background:#fef2f2}.json-editor-content{flex:1;display:flex;flex-direction:column;padding:16px 20px;min-height:200px}.json-textarea{flex:1;width:100%;min-height:250px;padding:12px;border:1px solid var(--border-color);border-radius:4px;font-family:Monaco,Menlo,Ubuntu Mono,Consolas,monospace;font-size:13px;line-height:1.5;resize:vertical;background:#fafafa}.json-textarea:focus{outline:none;border-color:var(--primary-color);background:#fff}.json-textarea.has-error{border-color:var(--error);background:#fef8f8}.json-error{margin-top:8px;padding:8px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:4px;color:var(--error);font-size:12px;font-family:monospace}.json-editor-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid var(--border-color);background:var(--background)}.json-edit-button{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;margin-left:4px;background:var(--primary-light);border:1px solid var(--primary-color);border-radius:3px;color:var(--primary-color);font-size:11px;cursor:pointer;transition:background .2s}.json-edit-button:hover{background:var(--primary-color);color:#fff}.reopen-folder-prompt{display:flex;flex-direction:column;gap:8px;padding:12px;margin:8px;background:var(--primary-light);border:1px solid var(--primary-color);border-radius:6px;font-size:13px}.reopen-folder-prompt span{color:var(--text-secondary)}.reopen-folder-prompt strong{color:var(--text-primary);word-break:break-all}.reopen-folder-prompt .btn{align-self:flex-start}.toast-container{position:fixed;top:16px;right:16px;z-index:10000;display:flex;flex-direction:column;gap:8px;max-width:400px}.toast{display:flex;align-items:center;gap:10px;padding:12px 16px;border-radius:6px;background:#fff;box-shadow:0 4px 12px #00000026;animation:toast-slide-in .3s ease}@keyframes toast-slide-in{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.toast-icon{flex-shrink:0;width:20px;height:20px;display:flex;align-items:center;justify-content:center;border-radius:50%;font-size:12px;font-weight:700}.toast-success .toast-icon{background:var(--success-light);color:var(--success)}.toast-error .toast-icon{background:#ffebee;color:var(--error)}.toast-warning .toast-icon{background:#fff3e0;color:var(--warning)}.toast-info .toast-icon{background:var(--primary-light);color:var(--primary-color)}.toast-message{flex:1;font-size:13px;color:var(--text-primary)}.toast-dismiss{flex-shrink:0;background:none;border:none;font-size:18px;color:var(--text-secondary);cursor:pointer;padding:0;line-height:1}.toast-dismiss:hover{color:var(--text-primary)}.variables-editor{display:flex;flex-direction:column;gap:8px}.variable-editor-item{display:flex;align-items:flex-start;gap:8px;padding:8px;background:var(--background);border-radius:4px}.variable-editor-item .var-name{flex:0 0 120px;position:relative}.variable-editor-item .var-value{flex:1;min-width:0}.variable-editor-item input,.variable-editor-item textarea{width:100%;padding:6px 8px;border:1px solid var(--border-color);border-radius:4px;font-size:13px;font-family:monospace}.variable-editor-item input:focus,.variable-editor-item textarea:focus{outline:none;border-color:var(--primary-color)}.variable-editor-item .var-actions{display:flex;gap:4px}.variable-editor-item .btn-icon{padding:4px 8px;background:none;border:1px solid var(--border-color);border-radius:4px;cursor:pointer;font-size:12px}.variable-editor-item .btn-icon:hover{background:var(--border-color)}.variable-editor-item .btn-icon.delete:hover{background:#ffebee;border-color:var(--error);color:var(--error)}.variable-editor-item.duplicate{background:#fff3e0}.variable-editor-item .duplicate-warning{position:absolute;right:6px;top:50%;transform:translateY(-50%);color:#f57c00;font-size:14px;cursor:help}.add-variable-btn{align-self:flex-start;padding:6px 12px;background:var(--primary-light);border:1px dashed var(--primary-color);border-radius:4px;color:var(--primary-color);font-size:13px;cursor:pointer}.add-variable-btn:hover{background:var(--primary-color);color:#fff;border-style:solid}.variables-section-header{display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border-color);margin-bottom:8px}.variables-section-header h4{margin:0;font-size:13px;font-weight:600;color:var(--text-primary)}.variables-empty{padding:16px;text-align:center;color:var(--text-secondary);font-size:13px;background:var(--background);border-radius:4px}.context-menu{position:fixed;background:#fff;border:1px solid var(--border-color);border-radius:6px;box-shadow:0 4px 12px #00000026;min-width:180px;z-index:10000;padding:4px 0}.context-menu-item{display:block;width:100%;padding:8px 12px;border:none;background:none;text-align:left;font-size:13px;color:var(--text-primary);cursor:pointer}.context-menu-item:hover:not(.disabled){background:var(--primary-light);color:var(--primary-color)}.context-menu-item.disabled{color:var(--text-secondary);cursor:not-allowed}.create-variable-dialog{background:var(--surface);border-radius:8px;min-width:400px;max-width:500px;box-shadow:0 4px 20px #00000026}.create-variable-dialog .modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color)}.create-variable-dialog .modal-header h2{margin:0;font-size:16px;font-weight:600}.create-variable-dialog .modal-close{background:none;border:none;font-size:24px;color:var(--text-secondary);cursor:pointer;padding:0;line-height:1}.create-variable-dialog .modal-close:hover{color:var(--text-primary)}.create-variable-dialog .modal-body{padding:16px 20px}.create-variable-dialog .form-group{margin-bottom:16px}.create-variable-dialog .form-group:last-child{margin-bottom:0}.create-variable-dialog .form-group label{display:block;font-size:13px;font-weight:500;margin-bottom:6px;color:var(--text-primary)}.create-variable-dialog .form-group code{background:var(--background);padding:2px 6px;border-radius:3px;font-size:12px}.create-variable-dialog .field-values-list{display:flex;flex-direction:column;gap:6px}.create-variable-dialog .field-value-option{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid var(--border-color);border-radius:4px;cursor:pointer;font-size:13px}.create-variable-dialog .field-value-option:hover{background:var(--background)}.create-variable-dialog .field-value-option input[type=radio]{margin:0}.create-variable-dialog .field-label{font-weight:500;color:var(--text-secondary);flex-shrink:0}.create-variable-dialog .field-value{color:var(--primary-color);font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.create-variable-dialog .single-field-value{display:flex;gap:8px;padding:8px 10px;background:var(--background);border-radius:4px;font-size:13px}.create-variable-dialog .variable-type-options{display:flex;flex-direction:column;gap:8px}.create-variable-dialog .type-option{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:10px 12px;border:1px solid var(--border-color);border-radius:4px;cursor:pointer}.create-variable-dialog .type-option:hover{background:var(--background)}.create-variable-dialog .type-option input[type=radio]{margin:0}.create-variable-dialog .type-option span:nth-child(2){font-weight:500}.create-variable-dialog .type-hint{width:100%;margin-left:22px;font-size:12px;color:var(--text-secondary)}.create-variable-dialog .form-group input[type=text]{width:100%;padding:8px 10px;border:1px solid var(--border-color);border-radius:4px;font-size:14px}.create-variable-dialog .form-group input[type=text]:focus{outline:none;border-color:var(--primary-color);box-shadow:0 0 0 2px var(--primary-light)}.create-variable-dialog .variable-preview{margin-top:6px;font-size:12px;color:var(--text-secondary)}.create-variable-dialog .variable-preview code{background:var(--success-light);color:var(--success)}.create-variable-dialog .modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid var(--border-color)}.prompt-dialog{min-width:400px;max-width:450px}.prompt-dialog .modal-header{display:flex;align-items:center;justify-content:space-between;padding-bottom:16px;margin-bottom:16px;border-bottom:1px solid var(--border-color)}.prompt-dialog .modal-header h2{margin:0;font-size:18px}.prompt-dialog .modal-body{padding:0}.prompt-field{margin-bottom:16px}.prompt-field:last-child{margin-bottom:0}.prompt-field label{display:block;margin-bottom:6px;font-weight:500;color:var(--text-primary)}.prompt-field input[type=text]{width:100%;padding:10px 12px;border:1px solid var(--border-color);border-radius:4px;font-size:14px;background:var(--bg-primary);color:var(--text-primary);box-sizing:border-box}.prompt-field input[type=text]:focus{outline:none;border-color:var(--primary-color);box-shadow:0 0 0 2px var(--primary-light)}
|