@testsmith/testblocks 0.9.7 → 0.9.9

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.
@@ -16,10 +16,30 @@
16
16
  overflow: hidden;
17
17
  }
18
18
  </style>
19
- <script type="module" crossorigin src="/assets/index-CMMJHs_d.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-B8OSvcUg.css">
19
+ <script type="module" crossorigin src="/assets/index-CSdQPHwK.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-CDAzk2fI.css">
21
21
  </head>
22
22
  <body>
23
+ <div id="chrome-warning" style="display:none;align-items:center;justify-content:center;height:100vh;background:#1e1e2e;color:#cdd6f4;font-family:system-ui,sans-serif;">
24
+ <div style="text-align:center;max-width:480px;padding:2rem;">
25
+ <div style="font-size:3rem;margin-bottom:1rem;">&#9888;&#65039;</div>
26
+ <h1 style="margin:0 0 1rem;font-size:1.5rem;">Chrome Required</h1>
27
+ <p style="margin:0 0 1.5rem;color:#a6adc8;line-height:1.6;">
28
+ TestBlocks only works in Google Chrome. Please open this page in Chrome to continue.
29
+ </p>
30
+ <a href="https://www.google.com/chrome/" target="_blank" rel="noopener noreferrer"
31
+ style="color:#89b4fa;text-decoration:underline;">Download Chrome</a>
32
+ </div>
33
+ </div>
23
34
  <div id="root"></div>
35
+ <script>
36
+ (function() {
37
+ var isChrome = /Chrome/.test(navigator.userAgent) && !/Edg/.test(navigator.userAgent);
38
+ if (!isChrome) {
39
+ document.getElementById('chrome-warning').style.display = 'flex';
40
+ document.getElementById('root').style.display = 'none';
41
+ }
42
+ })();
43
+ </script>
24
44
  </body>
25
45
  </html>
@@ -31,7 +31,7 @@ class ApiDeleteBlock extends base_1.StatementBlock {
31
31
  });
32
32
  const parsed = await (0, utils_1.parseResponse)(response);
33
33
  (0, utils_1.storeResponse)(context, parsed);
34
- return parsed;
34
+ return { ...parsed, _summary: `DELETE ${url}`, _requestHeaders: headers };
35
35
  }
36
36
  }
37
37
  exports.ApiDeleteBlock = ApiDeleteBlock;
@@ -30,7 +30,7 @@ class ApiGetBlock extends base_1.StatementBlock {
30
30
  });
31
31
  const parsed = await (0, utils_1.parseResponse)(response);
32
32
  (0, utils_1.storeResponse)(context, parsed);
33
- return parsed;
33
+ return { ...parsed, _summary: `GET ${url}`, _requestHeaders: headers };
34
34
  }
35
35
  }
36
36
  exports.ApiGetBlock = ApiGetBlock;
@@ -41,7 +41,8 @@ class ApiPatchBlock extends base_1.StatementBlock {
41
41
  });
42
42
  const parsed = await (0, utils_1.parseResponse)(response);
43
43
  (0, utils_1.storeResponse)(context, parsed);
44
- return parsed;
44
+ const requestHeaders = { 'Content-Type': 'application/json', ...inlineHeaders };
45
+ return { ...parsed, _summary: `PATCH ${url}`, _requestHeaders: requestHeaders, _requestBody: body };
45
46
  }
46
47
  }
47
48
  exports.ApiPatchBlock = ApiPatchBlock;
@@ -41,7 +41,8 @@ class ApiPostBlock extends base_1.StatementBlock {
41
41
  });
42
42
  const parsed = await (0, utils_1.parseResponse)(response);
43
43
  (0, utils_1.storeResponse)(context, parsed);
44
- return parsed;
44
+ const requestHeaders = { 'Content-Type': 'application/json', ...inlineHeaders };
45
+ return { ...parsed, _summary: `POST ${url}`, _requestHeaders: requestHeaders, _requestBody: body };
45
46
  }
46
47
  }
47
48
  exports.ApiPostBlock = ApiPostBlock;
@@ -41,7 +41,8 @@ class ApiPutBlock extends base_1.StatementBlock {
41
41
  });
42
42
  const parsed = await (0, utils_1.parseResponse)(response);
43
43
  (0, utils_1.storeResponse)(context, parsed);
44
- return parsed;
44
+ const requestHeaders = { 'Content-Type': 'application/json', ...inlineHeaders };
45
+ return { ...parsed, _summary: `PUT ${url}`, _requestHeaders: requestHeaders, _requestBody: body };
45
46
  }
46
47
  }
47
48
  exports.ApiPutBlock = ApiPutBlock;
@@ -8,6 +8,20 @@ export interface ExecutorOptions {
8
8
  testIdAttribute?: string;
9
9
  baseDir?: string;
10
10
  procedures?: Record<string, ProcedureDefinition>;
11
+ locale?: string;
12
+ timezoneId?: string;
13
+ geolocation?: {
14
+ latitude: number;
15
+ longitude: number;
16
+ };
17
+ viewport?: {
18
+ width: number;
19
+ height: number;
20
+ };
21
+ localStorage?: {
22
+ name: string;
23
+ value: string;
24
+ }[];
11
25
  }
12
26
  export declare class TestExecutor {
13
27
  private options;
@@ -121,10 +121,30 @@ class TestExecutor {
121
121
  this.browser = await playwright_1.chromium.launch({
122
122
  headless: this.options.headless,
123
123
  });
124
- // Use consistent viewport size for headless and headed modes
125
- this.context = await this.browser.newContext({
126
- viewport: { width: 1920, height: 1080 },
127
- });
124
+ // Build browser context options
125
+ const contextOptions = {
126
+ viewport: this.options.viewport || { width: 1920, height: 1080 },
127
+ };
128
+ if (this.options.locale) {
129
+ contextOptions.locale = this.options.locale;
130
+ }
131
+ if (this.options.timezoneId) {
132
+ contextOptions.timezoneId = this.options.timezoneId;
133
+ }
134
+ if (this.options.geolocation) {
135
+ contextOptions.geolocation = this.options.geolocation;
136
+ contextOptions.permissions = ['geolocation'];
137
+ }
138
+ console.log('Browser context options:', JSON.stringify(contextOptions));
139
+ this.context = await this.browser.newContext(contextOptions);
140
+ if (this.options.localStorage && this.options.localStorage.length > 0) {
141
+ const items = this.options.localStorage;
142
+ await this.context.addInitScript((storageItems) => {
143
+ for (const item of storageItems) {
144
+ localStorage.setItem(item.name, item.value);
145
+ }
146
+ }, items);
147
+ }
128
148
  this.page = await this.context.newPage();
129
149
  if (this.options.timeout) {
130
150
  this.page.setDefaultTimeout(this.options.timeout);
@@ -189,8 +209,8 @@ class TestExecutor {
189
209
  }
190
210
  const sharedContext = {
191
211
  variables: new Map(Object.entries({
192
- ...this.resolveVariableDefaults(testFile.variables),
193
212
  ...this.resolveVariableDefaults(this.options.variables),
213
+ ...this.resolveVariableDefaults(testFile.variables),
194
214
  })),
195
215
  results: [],
196
216
  browser: this.browser,
@@ -414,8 +434,8 @@ class TestExecutor {
414
434
  const baseVariables = sharedContext
415
435
  ? Object.fromEntries(sharedContext.variables)
416
436
  : {
417
- ...this.resolveVariableDefaults(fileVariables),
418
437
  ...this.resolveVariableDefaults(this.options.variables),
438
+ ...this.resolveVariableDefaults(fileVariables),
419
439
  };
420
440
  const context = {
421
441
  variables: new Map(Object.entries(baseVariables)),
@@ -504,8 +524,8 @@ class TestExecutor {
504
524
  const baseVariables = sharedContext
505
525
  ? Object.fromEntries(sharedContext.variables)
506
526
  : {
507
- ...this.resolveVariableDefaults(fileVariables),
508
527
  ...this.resolveVariableDefaults(this.options.variables),
528
+ ...this.resolveVariableDefaults(fileVariables),
509
529
  };
510
530
  const context = {
511
531
  variables: new Map(Object.entries(baseVariables)),
@@ -43,6 +43,7 @@ const path_1 = __importDefault(require("path"));
43
43
  const fs_1 = __importDefault(require("fs"));
44
44
  const executor_1 = require("./executor");
45
45
  const reporters_1 = require("../cli/reporters");
46
+ const openApiParser_1 = require("./openApiParser");
46
47
  // Read version from package.json
47
48
  function getVersion() {
48
49
  try {
@@ -390,6 +391,55 @@ async function startServer(options = {}) {
390
391
  (0, globals_1.setTestIdAttribute)(testIdAttribute);
391
392
  res.json({ testIdAttribute: (0, globals_1.getTestIdAttribute)() });
392
393
  });
394
+ // OpenAPI import endpoints
395
+ app.post('/api/openapi/parse', async (req, res) => {
396
+ try {
397
+ const { url } = req.body;
398
+ if (!url) {
399
+ return res.status(400).json({ error: 'URL is required' });
400
+ }
401
+ const spec = await (0, openApiParser_1.parseOpenApiSpec)(url, true);
402
+ res.json(spec);
403
+ }
404
+ catch (error) {
405
+ res.status(500).json({ error: 'Failed to parse spec', message: error.message });
406
+ }
407
+ });
408
+ app.post('/api/openapi/parse-content', async (req, res) => {
409
+ try {
410
+ const { content } = req.body;
411
+ if (!content) {
412
+ return res.status(400).json({ error: 'Content is required' });
413
+ }
414
+ const spec = await (0, openApiParser_1.parseOpenApiSpec)(content, false);
415
+ res.json(spec);
416
+ }
417
+ catch (error) {
418
+ res.status(500).json({ error: 'Failed to parse spec', message: error.message });
419
+ }
420
+ });
421
+ app.post('/api/openapi/generate', async (req, res) => {
422
+ try {
423
+ const { url, content, selectedEndpoints, options } = req.body;
424
+ if (!selectedEndpoints || selectedEndpoints.length === 0) {
425
+ return res.status(400).json({ error: 'No endpoints selected' });
426
+ }
427
+ const spec = url
428
+ ? await (0, openApiParser_1.parseOpenApiSpec)(url, true)
429
+ : await (0, openApiParser_1.parseOpenApiSpec)(content, false);
430
+ const files = (0, openApiParser_1.generateTestFiles)(spec, selectedEndpoints, options);
431
+ res.json({
432
+ files: files.map(f => ({
433
+ fileName: f.fileName,
434
+ testFile: f.testFile,
435
+ testCount: f.testFile.tests.length,
436
+ })),
437
+ });
438
+ }
439
+ catch (error) {
440
+ res.status(500).json({ error: 'Failed to generate tests', message: error.message });
441
+ }
442
+ });
393
443
  // Run tests
394
444
  app.post('/api/run', async (req, res) => {
395
445
  try {
@@ -403,14 +453,37 @@ async function startServer(options = {}) {
403
453
  const globalVars = (0, globals_1.getGlobalVariables)();
404
454
  const globalProcs = (0, globals_1.getGlobalProcedures)();
405
455
  const testIdAttr = (0, globals_1.getTestIdAttribute)();
406
- const executor = new executor_1.TestExecutor({
456
+ const executorOpts = {
407
457
  headless: req.query.headless !== 'false',
408
458
  timeout: Number(req.query.timeout) || (0, globals_1.getGlobalTimeout)(),
409
459
  variables: globalVars,
410
460
  procedures: globalProcs,
411
- testIdAttribute: testIdAttr,
461
+ testIdAttribute: req.query.testIdAttribute || testIdAttr,
412
462
  baseDir: globalsDir,
413
- });
463
+ };
464
+ if (req.query.locale)
465
+ executorOpts.locale = req.query.locale;
466
+ if (req.query.timezoneId)
467
+ executorOpts.timezoneId = req.query.timezoneId;
468
+ if (req.query.geoLatitude && req.query.geoLongitude) {
469
+ executorOpts.geolocation = {
470
+ latitude: parseFloat(req.query.geoLatitude),
471
+ longitude: parseFloat(req.query.geoLongitude),
472
+ };
473
+ }
474
+ if (req.query.viewportWidth && req.query.viewportHeight) {
475
+ executorOpts.viewport = {
476
+ width: parseInt(req.query.viewportWidth, 10),
477
+ height: parseInt(req.query.viewportHeight, 10),
478
+ };
479
+ }
480
+ if (req.query.localStorage) {
481
+ try {
482
+ executorOpts.localStorage = JSON.parse(req.query.localStorage);
483
+ }
484
+ catch { /* ignore invalid JSON */ }
485
+ }
486
+ const executor = new executor_1.TestExecutor(executorOpts);
414
487
  const results = await executor.runTestFile(mergedTestFile);
415
488
  const passed = results.filter(r => r.status === 'passed').length;
416
489
  const failed = results.filter(r => r.status === 'failed').length;
@@ -453,14 +526,37 @@ async function startServer(options = {}) {
453
526
  const globalVars = (0, globals_1.getGlobalVariables)();
454
527
  const globalProcs = (0, globals_1.getGlobalProcedures)();
455
528
  const testIdAttr = (0, globals_1.getTestIdAttribute)();
456
- const executor = new executor_1.TestExecutor({
529
+ const executorOpts = {
457
530
  headless: req.query.headless !== 'false',
458
531
  timeout: Number(req.query.timeout) || (0, globals_1.getGlobalTimeout)(),
459
532
  variables: globalVars,
460
533
  procedures: globalProcs,
461
- testIdAttribute: testIdAttr,
534
+ testIdAttribute: req.query.testIdAttribute || testIdAttr,
462
535
  baseDir: globalsDir,
463
- });
536
+ };
537
+ if (req.query.locale)
538
+ executorOpts.locale = req.query.locale;
539
+ if (req.query.timezoneId)
540
+ executorOpts.timezoneId = req.query.timezoneId;
541
+ if (req.query.geoLatitude && req.query.geoLongitude) {
542
+ executorOpts.geolocation = {
543
+ latitude: parseFloat(req.query.geoLatitude),
544
+ longitude: parseFloat(req.query.geoLongitude),
545
+ };
546
+ }
547
+ if (req.query.viewportWidth && req.query.viewportHeight) {
548
+ executorOpts.viewport = {
549
+ width: parseInt(req.query.viewportWidth, 10),
550
+ height: parseInt(req.query.viewportHeight, 10),
551
+ };
552
+ }
553
+ if (req.query.localStorage) {
554
+ try {
555
+ executorOpts.localStorage = JSON.parse(req.query.localStorage);
556
+ }
557
+ catch { /* ignore invalid JSON */ }
558
+ }
559
+ const executor = new executor_1.TestExecutor(executorOpts);
464
560
  // Register file-level procedures (overrides globals)
465
561
  if (mergedTestFile.procedures) {
466
562
  executor.registerProcedures(mergedTestFile.procedures);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testsmith/testblocks",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "Visual test automation tool with Blockly - API and Playwright testing",
5
5
  "author": "Roy de Kleijn",
6
6
  "license": "MIT",
@@ -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)}.openapi-dialog{min-width:600px;max-width:800px;max-height:85vh}.openapi-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)}.openapi-dialog .modal-header h2{margin:0;font-size:18px}.openapi-dialog .modal-body{min-height:300px;max-height:55vh;overflow-y:auto}.openapi-input-stage{display:flex;flex-direction:column;gap:16px}.input-mode-tabs{display:flex;border-bottom:1px solid var(--border-color);margin-bottom:8px}.input-mode-tabs .tab{padding:10px 20px;border:none;background:none;color:var(--text-secondary);font-size:14px;font-weight:500;cursor:pointer;border-bottom:2px solid transparent;transition:all .2s}.input-mode-tabs .tab:hover{color:var(--text-primary)}.input-mode-tabs .tab.active{color:var(--primary-color);border-bottom-color:var(--primary-color)}.url-input-section,.file-input-section{display:flex;flex-direction:column;gap:12px}.url-input-section label,.file-input-section label{font-weight:500;color:var(--text-primary)}.file-select-btn{align-self:flex-start;background:var(--background)!important;color:var(--text-primary)!important;border:1px solid var(--border-color)!important}.file-select-btn:hover{background:var(--border-color)!important}.file-selected{color:var(--success);font-size:13px}.input-hint{font-size:13px;color:var(--text-secondary);margin:0}.openapi-error{color:var(--error);font-size:13px;padding:10px 14px;background:#d32f2f14;border-radius:4px;border-left:3px solid var(--error)}.openapi-loading-stage{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:40px 20px}.openapi-loading-stage p{color:var(--text-secondary);font-size:14px}.openapi-selection-stage{display:flex;flex-direction:column;gap:16px}.spec-info{background:var(--background);padding:12px 16px;border-radius:6px}.spec-info h3{margin:0;font-size:16px;color:var(--text-primary)}.spec-info .version{font-weight:400;font-size:13px;color:var(--text-secondary);margin-left:8px}.spec-description{margin:8px 0 0;font-size:13px;color:var(--text-secondary);line-height:1.4}.endpoint-filters{display:flex;flex-direction:column;gap:10px}.filter-row{display:flex;gap:8px}.search-input{flex:1;padding:8px 12px;border:1px solid var(--border-color);border-radius:4px;font-size:13px}.search-input:focus{outline:none;border-color:var(--primary-color)}.filter-select{padding:8px 12px;border:1px solid var(--border-color);border-radius:4px;font-size:13px;background:#fff;min-width:120px}.filter-select:focus{outline:none;border-color:var(--primary-color)}.selection-actions{display:flex;align-items:center;gap:12px}.btn-small{padding:6px 12px;font-size:12px;background:var(--background);border:1px solid var(--border-color);border-radius:4px;color:var(--text-primary);cursor:pointer}.btn-small:hover{background:var(--border-color)}.selection-count{margin-left:auto;font-size:13px;color:var(--text-secondary)}.endpoint-list{max-height:300px;overflow-y:auto;border:1px solid var(--border-color);border-radius:6px;background:#fff}.endpoint-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid var(--border-color);cursor:pointer;transition:background .15s}.endpoint-item:last-child{border-bottom:none}.endpoint-item:hover{background:var(--background)}.endpoint-item.selected{background:#1976d214}.endpoint-item.deprecated{opacity:.6}.endpoint-item input[type=checkbox]{flex-shrink:0}.method-badge{padding:3px 8px;border-radius:4px;font-size:11px;font-weight:600;color:#fff;text-transform:uppercase;flex-shrink:0;min-width:55px;text-align:center}.endpoint-path{font-family:monospace;font-size:13px;color:var(--text-primary);flex-shrink:0}.endpoint-summary{font-size:12px;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.deprecated-badge{font-size:10px;background:var(--warning);color:#fff;padding:2px 6px;border-radius:3px;flex-shrink:0}.endpoint-tags{display:flex;gap:4px;flex-shrink:0}.tag-badge{font-size:10px;background:var(--primary-light);color:var(--primary-color);padding:2px 6px;border-radius:3px}.no-endpoints{padding:24px;text-align:center;color:var(--text-secondary);font-size:14px}.openapi-options-stage{display:flex;flex-direction:column;gap:20px}.openapi-options-stage h3{margin:0 0 4px;font-size:16px}.option-group{display:flex;flex-direction:column;gap:8px}.option-group>label{font-weight:500;color:var(--text-primary);font-size:14px}.option-group .option-input{padding:10px 12px;border:1px solid var(--border-color);border-radius:4px;font-size:14px}.option-group .option-input:focus{outline:none;border-color:var(--primary-color)}.option-group .option-hint{font-size:12px;color:var(--text-secondary);margin:0}.radio-group{display:flex;flex-direction:column;gap:8px}.radio-option{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--border-color);border-radius:4px;cursor:pointer;transition:all .15s}.radio-option:hover{background:var(--background)}.radio-option input[type=radio]{margin:0}.radio-option span:first-of-type{font-weight:500}.radio-option .option-desc{font-size:12px;color:var(--text-secondary);margin-left:auto}.checkbox-group .checkbox-option{display:flex;align-items:center;gap:10px;cursor:pointer}.checkbox-group .checkbox-option input[type=checkbox]{margin:0}.openapi-preview-stage{display:flex;flex-direction:column;gap:16px}.openapi-preview-stage h3{margin:0;font-size:16px}.preview-summary{font-size:14px;color:var(--text-secondary);margin:0}.generated-files-list{display:flex;flex-direction:column;gap:12px}.generated-file-item{border:1px solid var(--border-color);border-radius:6px;overflow:hidden}.generated-file-item .file-header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:var(--background);border-bottom:1px solid var(--border-color)}.generated-file-item .file-name{font-weight:600;font-size:13px;color:var(--text-primary);font-family:monospace}.generated-file-item .test-count{font-size:12px;color:var(--text-secondary)}.generated-file-item .file-tests{padding:8px 14px;max-height:150px;overflow-y:auto}.preview-test{padding:6px 0;font-size:13px;color:var(--text-primary);border-bottom:1px solid var(--border-color)}.preview-test:last-child{border-bottom:none}.more-tests{padding:8px 0;font-size:12px;color:var(--text-secondary);font-style:italic}.openapi-error-stage{display:flex;flex-direction:column;align-items:center;gap:16px;padding:20px}.openapi-error-stage h3{margin:0;color:var(--error)}.openapi-warning{color:var(--warning);font-size:13px;padding:10px 14px;background:#e67e001a;border-radius:4px;border-left:3px solid var(--warning);margin-bottom:16px}.welcome-screen{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:40px;text-align:center;background:linear-gradient(135deg,var(--background) 0%,#e8f4fc 100%)}.welcome-content{max-width:500px}.welcome-screen h2{font-size:28px;color:var(--primary-color);margin-bottom:12px}.welcome-screen>.welcome-content>p{color:var(--text-secondary);font-size:16px;margin-bottom:24px}.reopen-section{background:var(--surface);border:2px solid var(--primary-color);border-radius:12px;padding:24px;margin-bottom:24px;box-shadow:0 4px 12px #205d9626}.reopen-section .reopen-message{font-size:14px;color:var(--text-secondary);margin-bottom:16px}.reopen-btn{font-size:16px!important;padding:14px 28px!important}.welcome-actions{display:flex;gap:16px;justify-content:center;margin-bottom:32px}.btn-large{padding:12px 24px!important;font-size:15px!important}.welcome-tip{font-size:13px;color:var(--text-secondary);background:var(--surface);padding:16px;border-radius:8px;border:1px solid var(--border-color)}.welcome-tip code{background:var(--background);padding:2px 6px;border-radius:4px;font-family:SF Mono,Monaco,Cascadia Code,monospace;font-size:12px}