@wundr.io/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +551 -0
- package/bin/wundr.js +39 -0
- package/dist/ai/ai-service.d.ts +152 -0
- package/dist/ai/ai-service.d.ts.map +1 -0
- package/dist/ai/ai-service.js +430 -0
- package/dist/ai/ai-service.js.map +1 -0
- package/dist/ai/claude-client.d.ts +130 -0
- package/dist/ai/claude-client.d.ts.map +1 -0
- package/dist/ai/claude-client.js +339 -0
- package/dist/ai/claude-client.js.map +1 -0
- package/dist/ai/conversation-manager.d.ts +164 -0
- package/dist/ai/conversation-manager.d.ts.map +1 -0
- package/dist/ai/conversation-manager.js +612 -0
- package/dist/ai/conversation-manager.js.map +1 -0
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +8 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/cli.d.ts +36 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +173 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ai.d.ts +89 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +735 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/analyze-optimized.d.ts +14 -0
- package/dist/commands/analyze-optimized.d.ts.map +1 -0
- package/dist/commands/analyze-optimized.js +437 -0
- package/dist/commands/analyze-optimized.js.map +1 -0
- package/dist/commands/analyze.d.ts +65 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/batch.d.ts +71 -0
- package/dist/commands/batch.d.ts.map +1 -0
- package/dist/commands/batch.js +738 -0
- package/dist/commands/batch.js.map +1 -0
- package/dist/commands/chat.d.ts +71 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +674 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/claude-init.d.ts +28 -0
- package/dist/commands/claude-init.d.ts.map +1 -0
- package/dist/commands/claude-init.js +587 -0
- package/dist/commands/claude-init.js.map +1 -0
- package/dist/commands/claude-setup.d.ts +32 -0
- package/dist/commands/claude-setup.d.ts.map +1 -0
- package/dist/commands/claude-setup.js +570 -0
- package/dist/commands/claude-setup.js.map +1 -0
- package/dist/commands/computer-setup-commands.d.ts +39 -0
- package/dist/commands/computer-setup-commands.d.ts.map +1 -0
- package/dist/commands/computer-setup-commands.js +563 -0
- package/dist/commands/computer-setup-commands.js.map +1 -0
- package/dist/commands/computer-setup.d.ts +7 -0
- package/dist/commands/computer-setup.d.ts.map +1 -0
- package/dist/commands/computer-setup.js +481 -0
- package/dist/commands/computer-setup.js.map +1 -0
- package/dist/commands/create-command.d.ts +7 -0
- package/dist/commands/create-command.d.ts.map +1 -0
- package/dist/commands/create-command.js +158 -0
- package/dist/commands/create-command.js.map +1 -0
- package/dist/commands/create.d.ts +74 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +556 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dashboard.d.ts +91 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +537 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/govern.d.ts +70 -0
- package/dist/commands/govern.d.ts.map +1 -0
- package/dist/commands/govern.js +480 -0
- package/dist/commands/govern.js.map +1 -0
- package/dist/commands/init.d.ts +55 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +584 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/performance-optimizer.d.ts +30 -0
- package/dist/commands/performance-optimizer.d.ts.map +1 -0
- package/dist/commands/performance-optimizer.js +649 -0
- package/dist/commands/performance-optimizer.js.map +1 -0
- package/dist/commands/plugins.d.ts +87 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +685 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/setup.d.ts +29 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +399 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/test-init.d.ts +9 -0
- package/dist/commands/test-init.d.ts.map +1 -0
- package/dist/commands/test-init.js +222 -0
- package/dist/commands/test-init.js.map +1 -0
- package/dist/commands/test.d.ts +25 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +217 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/watch.d.ts +76 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +610 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/context/context-manager.d.ts +155 -0
- package/dist/context/context-manager.d.ts.map +1 -0
- package/dist/context/context-manager.js +383 -0
- package/dist/context/context-manager.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +6 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/session-manager.d.ts +207 -0
- package/dist/context/session-manager.d.ts.map +1 -0
- package/dist/context/session-manager.js +682 -0
- package/dist/context/session-manager.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive/interactive-mode.d.ts +76 -0
- package/dist/interactive/interactive-mode.d.ts.map +1 -0
- package/dist/interactive/interactive-mode.js +730 -0
- package/dist/interactive/interactive-mode.js.map +1 -0
- package/dist/nlp/command-mapper.d.ts +174 -0
- package/dist/nlp/command-mapper.d.ts.map +1 -0
- package/dist/nlp/command-mapper.js +623 -0
- package/dist/nlp/command-mapper.js.map +1 -0
- package/dist/nlp/command-parser.d.ts +106 -0
- package/dist/nlp/command-parser.d.ts.map +1 -0
- package/dist/nlp/command-parser.js +416 -0
- package/dist/nlp/command-parser.js.map +1 -0
- package/dist/nlp/index.d.ts +5 -0
- package/dist/nlp/index.d.ts.map +1 -0
- package/dist/nlp/index.js +8 -0
- package/dist/nlp/index.js.map +1 -0
- package/dist/nlp/intent-classifier.d.ts +59 -0
- package/dist/nlp/intent-classifier.d.ts.map +1 -0
- package/dist/nlp/intent-classifier.js +384 -0
- package/dist/nlp/intent-classifier.js.map +1 -0
- package/dist/nlp/intent-parser.d.ts +152 -0
- package/dist/nlp/intent-parser.d.ts.map +1 -0
- package/dist/nlp/intent-parser.js +739 -0
- package/dist/nlp/intent-parser.js.map +1 -0
- package/dist/plugins/plugin-manager.d.ts +120 -0
- package/dist/plugins/plugin-manager.d.ts.map +1 -0
- package/dist/plugins/plugin-manager.js +595 -0
- package/dist/plugins/plugin-manager.js.map +1 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/config-manager.d.ts +73 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +339 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/error-handler.d.ts +46 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +169 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +94 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +119 -0
- package/src/ai/ai-service.ts +595 -0
- package/src/ai/claude-client.ts +490 -0
- package/src/ai/conversation-manager.ts +907 -0
- package/src/ai/index.ts +8 -0
- package/src/cli.ts +202 -0
- package/src/commands/ai.ts +995 -0
- package/src/commands/analyze-optimized.ts +641 -0
- package/src/commands/analyze.ts +576 -0
- package/src/commands/batch.ts +935 -0
- package/src/commands/chat.ts +876 -0
- package/src/commands/claude-init.ts +715 -0
- package/src/commands/claude-setup.ts +697 -0
- package/src/commands/computer-setup-commands.ts +709 -0
- package/src/commands/computer-setup.ts +565 -0
- package/src/commands/create-command.ts +175 -0
- package/src/commands/create.ts +727 -0
- package/src/commands/dashboard.ts +691 -0
- package/src/commands/govern.ts +635 -0
- package/src/commands/init.ts +677 -0
- package/src/commands/performance-optimizer.ts +864 -0
- package/src/commands/plugins.ts +848 -0
- package/src/commands/setup.ts +508 -0
- package/src/commands/test-init.ts +242 -0
- package/src/commands/test.ts +264 -0
- package/src/commands/watch.ts +755 -0
- package/src/context/context-manager.ts +546 -0
- package/src/context/index.ts +9 -0
- package/src/context/session-manager.ts +1019 -0
- package/src/index.ts +64 -0
- package/src/interactive/interactive-mode.ts +830 -0
- package/src/nlp/command-mapper.ts +885 -0
- package/src/nlp/command-parser.ts +564 -0
- package/src/nlp/index.ts +4 -0
- package/src/nlp/intent-classifier.ts +458 -0
- package/src/nlp/intent-parser.ts +1101 -0
- package/src/plugins/plugin-manager.ts +744 -0
- package/src/types/index.ts +252 -0
- package/src/types/modules.d.ts +56 -0
- package/src/utils/config-manager.ts +391 -0
- package/src/utils/error-handler.ts +192 -0
- package/src/utils/logger.ts +104 -0
- package/templates/batch/ci-cd.yaml +62 -0
- package/templates/component/{{fileName}}.test.tsx +17 -0
- package/templates/component/{{fileName}}.tsx +21 -0
- package/templates/service/{{fileName}}.ts +98 -0
- package/templates/wundr-test.config.js +0 -0
- package/test-suites/api/health.spec.ts +134 -0
- package/test-suites/helpers/test-config.ts +84 -0
- package/test-suites/ui/accessibility.spec.ts +102 -0
- package/test-suites/ui/smoke.spec.ts +92 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable test configuration
|
|
3
|
+
* Can be customized via wundr-test.config.js in the target project
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface TestConfig {
|
|
7
|
+
baseURL: string;
|
|
8
|
+
timeout: number;
|
|
9
|
+
retries: number;
|
|
10
|
+
headless: boolean;
|
|
11
|
+
slowMo?: number;
|
|
12
|
+
screenshot: 'off' | 'on' | 'only-on-failure';
|
|
13
|
+
video: 'off' | 'on' | 'retain-on-failure';
|
|
14
|
+
trace: 'off' | 'on' | 'on-first-retry';
|
|
15
|
+
|
|
16
|
+
// Custom selectors for specific apps
|
|
17
|
+
selectors?: {
|
|
18
|
+
navigation?: string;
|
|
19
|
+
mainContent?: string;
|
|
20
|
+
footer?: string;
|
|
21
|
+
searchInput?: string;
|
|
22
|
+
loginButton?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// API configuration
|
|
26
|
+
api?: {
|
|
27
|
+
baseURL?: string;
|
|
28
|
+
headers?: Record<string, string>;
|
|
29
|
+
timeout?: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Test data
|
|
33
|
+
testData?: {
|
|
34
|
+
validUser?: {
|
|
35
|
+
username: string;
|
|
36
|
+
password: string;
|
|
37
|
+
};
|
|
38
|
+
searchTerms?: string[];
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const defaultConfig: TestConfig = {
|
|
43
|
+
baseURL: process.env.TEST_BASE_URL || 'http://localhost:3000',
|
|
44
|
+
timeout: 30000,
|
|
45
|
+
retries: 2,
|
|
46
|
+
headless: true,
|
|
47
|
+
screenshot: 'only-on-failure',
|
|
48
|
+
video: 'retain-on-failure',
|
|
49
|
+
trace: 'on-first-retry',
|
|
50
|
+
|
|
51
|
+
selectors: {
|
|
52
|
+
navigation: 'nav, [role="navigation"], header',
|
|
53
|
+
mainContent: 'main, [role="main"], #content',
|
|
54
|
+
footer: 'footer, [role="contentinfo"]',
|
|
55
|
+
searchInput: 'input[type="search"], input[placeholder*="search" i]',
|
|
56
|
+
loginButton: 'button[type="submit"], button:has-text("Login"), button:has-text("Sign in")'
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
api: {
|
|
60
|
+
timeout: 10000
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Load configuration from project or use defaults
|
|
66
|
+
*/
|
|
67
|
+
export function loadConfig(customConfig?: Partial<TestConfig>): TestConfig {
|
|
68
|
+
return {
|
|
69
|
+
...defaultConfig,
|
|
70
|
+
...customConfig,
|
|
71
|
+
selectors: {
|
|
72
|
+
...defaultConfig.selectors,
|
|
73
|
+
...customConfig?.selectors
|
|
74
|
+
},
|
|
75
|
+
api: {
|
|
76
|
+
...defaultConfig.api,
|
|
77
|
+
...customConfig?.api
|
|
78
|
+
},
|
|
79
|
+
testData: {
|
|
80
|
+
...defaultConfig.testData,
|
|
81
|
+
...customConfig?.testData
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Portable accessibility tests using axe-core
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
test.describe('Accessibility Tests', () => {
|
|
9
|
+
test('homepage meets WCAG standards', async ({ page }) => {
|
|
10
|
+
await page.goto('/');
|
|
11
|
+
|
|
12
|
+
const accessibilityScanResults = await new AxeBuilder({ page })
|
|
13
|
+
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
|
|
14
|
+
.analyze();
|
|
15
|
+
|
|
16
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('all images have alt text', async ({ page }) => {
|
|
20
|
+
await page.goto('/');
|
|
21
|
+
|
|
22
|
+
const images = await page.locator('img').all();
|
|
23
|
+
|
|
24
|
+
for (const img of images) {
|
|
25
|
+
const alt = await img.getAttribute('alt');
|
|
26
|
+
const role = await img.getAttribute('role');
|
|
27
|
+
|
|
28
|
+
// Images should have alt text or be marked as decorative
|
|
29
|
+
expect(alt !== null || role === 'presentation').toBeTruthy();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('forms have proper labels', async ({ page }) => {
|
|
34
|
+
await page.goto('/');
|
|
35
|
+
|
|
36
|
+
const inputs = await page.locator('input, select, textarea').all();
|
|
37
|
+
|
|
38
|
+
for (const input of inputs) {
|
|
39
|
+
const id = await input.getAttribute('id');
|
|
40
|
+
const ariaLabel = await input.getAttribute('aria-label');
|
|
41
|
+
const ariaLabelledBy = await input.getAttribute('aria-labelledby');
|
|
42
|
+
|
|
43
|
+
if (id) {
|
|
44
|
+
// Check for associated label
|
|
45
|
+
const label = await page.locator(`label[for="${id}"]`).count();
|
|
46
|
+
const hasLabel = label > 0 || ariaLabel !== null || ariaLabelledBy !== null;
|
|
47
|
+
expect(hasLabel).toBeTruthy();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('focus is visible and logical', async ({ page }) => {
|
|
53
|
+
await page.goto('/');
|
|
54
|
+
|
|
55
|
+
// Tab through interactive elements
|
|
56
|
+
await page.keyboard.press('Tab');
|
|
57
|
+
|
|
58
|
+
// Check if focused element has visible outline
|
|
59
|
+
const focusedElement = await page.evaluateHandle(() => document.activeElement);
|
|
60
|
+
const hasOutline = await focusedElement.evaluate((el) => {
|
|
61
|
+
if (!el) return false;
|
|
62
|
+
const styles = window.getComputedStyle(el as Element);
|
|
63
|
+
return styles.outlineStyle !== 'none' || styles.boxShadow !== 'none';
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(hasOutline).toBeTruthy();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('color contrast meets standards', async ({ page }) => {
|
|
70
|
+
await page.goto('/');
|
|
71
|
+
|
|
72
|
+
const accessibilityScanResults = await new AxeBuilder({ page })
|
|
73
|
+
.withTags(['color-contrast'])
|
|
74
|
+
.analyze();
|
|
75
|
+
|
|
76
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('page has proper heading structure', async ({ page }) => {
|
|
80
|
+
await page.goto('/');
|
|
81
|
+
|
|
82
|
+
// Check for h1
|
|
83
|
+
const h1Count = await page.locator('h1').count();
|
|
84
|
+
expect(h1Count).toBeGreaterThan(0);
|
|
85
|
+
|
|
86
|
+
// Check heading hierarchy
|
|
87
|
+
const headings = await page.evaluate(() => {
|
|
88
|
+
const headingElements = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
|
89
|
+
return Array.from(headingElements).map(h => ({
|
|
90
|
+
level: parseInt(h.tagName[1]),
|
|
91
|
+
text: h.textContent
|
|
92
|
+
}));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Verify no skipped heading levels
|
|
96
|
+
let previousLevel = 0;
|
|
97
|
+
for (const heading of headings) {
|
|
98
|
+
expect(heading.level - previousLevel).toBeLessThanOrEqual(1);
|
|
99
|
+
previousLevel = heading.level;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Portable smoke tests that can be run against any web application
|
|
5
|
+
* These tests are generic and work with most web apps
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
test.describe('Portable Smoke Tests', () => {
|
|
9
|
+
test('homepage loads without errors', async ({ page }) => {
|
|
10
|
+
const jsErrors: string[] = [];
|
|
11
|
+
|
|
12
|
+
page.on('pageerror', (error) => {
|
|
13
|
+
jsErrors.push(error.message);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await page.goto('/');
|
|
17
|
+
await page.waitForLoadState('networkidle');
|
|
18
|
+
|
|
19
|
+
// Basic structure should be present
|
|
20
|
+
await expect(page.locator('body')).toBeVisible();
|
|
21
|
+
|
|
22
|
+
// Should not have critical JavaScript errors
|
|
23
|
+
const criticalErrors = jsErrors.filter(error =>
|
|
24
|
+
error.includes('Cannot read') ||
|
|
25
|
+
error.includes('undefined is not') ||
|
|
26
|
+
error.includes('Uncaught')
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(criticalErrors.length).toBe(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('navigation elements are present', async ({ page }) => {
|
|
33
|
+
await page.goto('/');
|
|
34
|
+
|
|
35
|
+
// Check for common navigation patterns
|
|
36
|
+
const hasNavigation = await page.locator('nav, [role="navigation"], header').count() > 0;
|
|
37
|
+
expect(hasNavigation).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('interactive elements are clickable', async ({ page }) => {
|
|
41
|
+
await page.goto('/');
|
|
42
|
+
|
|
43
|
+
// Find clickable elements
|
|
44
|
+
const buttons = await page.locator('button:visible, a:visible').all();
|
|
45
|
+
|
|
46
|
+
if (buttons.length > 0) {
|
|
47
|
+
// Test first button/link
|
|
48
|
+
const firstElement = buttons[0];
|
|
49
|
+
await expect(firstElement).toBeEnabled();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('forms accept input', async ({ page }) => {
|
|
54
|
+
await page.goto('/');
|
|
55
|
+
|
|
56
|
+
// Look for form inputs
|
|
57
|
+
const inputs = await page.locator('input:visible, textarea:visible').all();
|
|
58
|
+
|
|
59
|
+
if (inputs.length > 0) {
|
|
60
|
+
const firstInput = inputs[0];
|
|
61
|
+
await firstInput.fill('test');
|
|
62
|
+
const value = await firstInput.inputValue();
|
|
63
|
+
expect(value).toBe('test');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('responsive layout works', async ({ page }) => {
|
|
68
|
+
await page.goto('/');
|
|
69
|
+
|
|
70
|
+
// Test mobile viewport
|
|
71
|
+
await page.setViewportSize({ width: 375, height: 667 });
|
|
72
|
+
|
|
73
|
+
// Should not have horizontal scroll
|
|
74
|
+
const hasHorizontalScroll = await page.evaluate(() => {
|
|
75
|
+
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(hasHorizontalScroll).toBeFalsy();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('page has proper metadata', async ({ page }) => {
|
|
82
|
+
await page.goto('/');
|
|
83
|
+
|
|
84
|
+
// Check for title
|
|
85
|
+
const title = await page.title();
|
|
86
|
+
expect(title).toBeTruthy();
|
|
87
|
+
|
|
88
|
+
// Check for language attribute
|
|
89
|
+
const lang = await page.locator('html').getAttribute('lang');
|
|
90
|
+
expect(lang).toBeTruthy();
|
|
91
|
+
});
|
|
92
|
+
});
|