This commit is contained in:
seb
2025-07-17 05:32:02 +02:00
commit a492223e45
24 changed files with 3880 additions and 0 deletions

47
.vscode/extensions/README.md vendored Normal file
View File

@@ -0,0 +1,47 @@
# PlayWrong VS Code Extension
This extension provides syntax highlighting for PlayWrong test files (.test).
## Installation
1. Copy the `playwrong-syntax` folder to your VS Code extensions directory:
- **Windows**: `%USERPROFILE%\.vscode\extensions\`
- **macOS**: `~/.vscode/extensions/`
- **Linux**: `~/.vscode/extensions/`
2. Restart VS Code
3. Open any `.test` file and enjoy syntax highlighting!
4. (Optional) Enable the PlayWrong theme for better colors:
- Press `Ctrl+Shift+P` → "Preferences: Color Theme"
- Select "PlayWrong Theme"
## Features
- **Syntax Highlighting**: Keywords, commands, strings, numbers, and variables
- **Comment Support**: Use `#` for line comments and `/* */` for multi-line comments
- **Auto-completion**: Bracket and quote auto-closing
- **Variable Highlighting**: `$VARIABLE` and `${VARIABLE}` syntax
- **Custom Theme**: Optimized colors for PlayWrong syntax
## Supported Commands
- `use`, `open`, `wait`, `click`, `fill`, `scroll`, `sleep`, `dump`, `break`
- Element selectors: `element`, `childText`, `class`, `id`, `name`, `href`, `type`, `value`, `child`
- Environment variables: `$PASSWORD`, `${API_KEY}`
## Example
```playwrong
/*
Multi-line comment block
This test demonstrates login functionality
*/
# This is a line comment
use "Chrome"
open "data:text/html,<html><body><h1>Comment Test</h1></body></html>"
fill element=input type="password" value="$PASSWORD"
click element=button childText="Login" /* inline comment */
sleep 2000 "wait for page load"
```

View File

@@ -0,0 +1,64 @@
# PlayWrong Test Language Extension
A VSCode extension that provides syntax highlighting and real-time linting for PlayWrong test files (`.test` extension).
## Features
### Syntax Highlighting
- Commands: `use`, `open`, `wait`, `click`, `scroll`, `fill`, `break`, `sleep`, `dump`
- Parameters: `element`, `name`, `id`, `class`, `href`, `type`, `childText`, `value`
- Strings with environment variable support (`$VARIABLE`)
- Comments: `//`, `#`, `/* */`
- Numbers and HTML elements
### Real-time Linting
- **Errors**: Invalid commands, syntax errors, missing parameters
- **Warnings**: Semantic issues, best practice violations
- **Info**: Helpful suggestions and environment variable usage
### Commands
- **PlayWrong: Lint PlayWrong Test File** - Run linter on current file
- **PlayWrong: Lint PlayWrong Test File (Strict)** - Run linter with warnings as errors
## Usage
1. Open any `.test` file in VSCode
2. The extension will automatically:
- Apply syntax highlighting
- Show real-time linting feedback in the Problems panel
- Underline errors, warnings, and info messages in the editor
3. Right-click in a `.test` file to access linting commands from the context menu
## Installation
This extension is automatically loaded when you open this workspace. The `.test` files will be automatically associated with the PlayWrong language.
## Example
```test
use "Chrome"
open "https://example.com"
wait element=button childText="Login"
click element=button childText="Login"
fill element=input type="email" value="user@example.com"
sleep 2000 "login processing"
dump "login_page"
```
## Linting Rules
### Errors
- Invalid commands
- Unclosed quotes
- Missing required parameters
### Warnings
- Unknown HTML elements
- Generic element selectors
- Semantic misuse (e.g., `click` on input fields)
### Info
- Environment variable usage
- Command placement suggestions
- Best practice tips

View File

@@ -0,0 +1,213 @@
const vscode = require('vscode');
const path = require('path');
// Import the linter from the main project
const TestLinter = require('../../../src/linter');
class PlayWrongDiagnosticProvider {
constructor() {
this.linter = new TestLinter();
this.diagnosticCollection = vscode.languages.createDiagnosticCollection('playwrong');
}
provideDiagnostics(document) {
if (document.languageId !== 'playwrong') {
return;
}
const text = document.getText();
const lintResult = this.linter.lint(text, document.fileName);
const diagnostics = [];
// Convert errors to diagnostics
lintResult.errors.forEach(error => {
const line = Math.max(0, error.line - 1); // Convert to 0-based
const range = new vscode.Range(line, 0, line, Number.MAX_VALUE);
const diagnostic = new vscode.Diagnostic(
range,
error.message,
vscode.DiagnosticSeverity.Error
);
diagnostic.source = 'PlayWrong Linter';
diagnostics.push(diagnostic);
});
// Convert warnings to diagnostics
lintResult.warnings.forEach(warning => {
const line = Math.max(0, warning.line - 1); // Convert to 0-based
const range = new vscode.Range(line, 0, line, Number.MAX_VALUE);
const diagnostic = new vscode.Diagnostic(
range,
warning.message,
vscode.DiagnosticSeverity.Warning
);
diagnostic.source = 'PlayWrong Linter';
diagnostics.push(diagnostic);
});
// Convert info to diagnostics (as information)
lintResult.info.forEach(info => {
const line = Math.max(0, info.line - 1); // Convert to 0-based
const range = new vscode.Range(line, 0, line, Number.MAX_VALUE);
const diagnostic = new vscode.Diagnostic(
range,
info.message,
vscode.DiagnosticSeverity.Information
);
diagnostic.source = 'PlayWrong Linter';
diagnostics.push(diagnostic);
});
this.diagnosticCollection.set(document.uri, diagnostics);
}
clear() {
this.diagnosticCollection.clear();
}
dispose() {
this.diagnosticCollection.dispose();
}
}
class PlayWrongExtension {
constructor() {
this.diagnosticProvider = new PlayWrongDiagnosticProvider();
}
activate(context) {
console.log('PlayWrong extension is now active!');
// Register diagnostic provider
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor) {
this.diagnosticProvider.provideDiagnostics(activeEditor.document);
}
// Listen for document changes
const onDidChangeTextDocument = vscode.workspace.onDidChangeTextDocument(event => {
if (event.document.languageId === 'playwrong') {
this.diagnosticProvider.provideDiagnostics(event.document);
}
});
// Listen for document open
const onDidOpenTextDocument = vscode.workspace.onDidOpenTextDocument(document => {
if (document.languageId === 'playwrong') {
this.diagnosticProvider.provideDiagnostics(document);
}
});
// Listen for active editor changes
const onDidChangeActiveTextEditor = vscode.window.onDidChangeActiveTextEditor(editor => {
if (editor && editor.document.languageId === 'playwrong') {
this.diagnosticProvider.provideDiagnostics(editor.document);
}
});
// Register commands
const lintCommand = vscode.commands.registerCommand('playwrong.lint', () => {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.languageId !== 'playwrong') {
vscode.window.showWarningMessage('Please open a PlayWrong test file (.test) to lint.');
return;
}
this.runLintCommand(editor.document, false);
});
const lintStrictCommand = vscode.commands.registerCommand('playwrong.lintStrict', () => {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.languageId !== 'playwrong') {
vscode.window.showWarningMessage('Please open a PlayWrong test file (.test) to lint.');
return;
}
this.runLintCommand(editor.document, true);
});
// Add disposables to context
context.subscriptions.push(
this.diagnosticProvider,
onDidChangeTextDocument,
onDidOpenTextDocument,
onDidChangeActiveTextEditor,
lintCommand,
lintStrictCommand
);
// Initial lint for already open documents
vscode.workspace.textDocuments.forEach(document => {
if (document.languageId === 'playwrong') {
this.diagnosticProvider.provideDiagnostics(document);
}
});
}
runLintCommand(document, strict) {
const text = document.getText();
const lintResult = this.diagnosticProvider.linter.lint(text, document.fileName);
let message = '';
let hasIssues = false;
if (lintResult.errors.length > 0) {
message += `${lintResult.errors.length} error(s)`;
hasIssues = true;
}
if (lintResult.warnings.length > 0) {
if (message) message += ', ';
message += `⚠️ ${lintResult.warnings.length} warning(s)`;
if (strict) hasIssues = true;
}
if (lintResult.info.length > 0) {
if (message) message += ', ';
message += `💡 ${lintResult.info.length} info`;
}
if (!message) {
message = '✅ No issues found';
}
const fileName = path.basename(document.fileName);
const fullMessage = `${fileName}: ${message}`;
if (hasIssues) {
if (lintResult.errors.length > 0) {
vscode.window.showErrorMessage(fullMessage);
} else {
vscode.window.showWarningMessage(fullMessage);
}
} else {
vscode.window.showInformationMessage(fullMessage);
}
// Update diagnostics
this.diagnosticProvider.provideDiagnostics(document);
}
deactivate() {
this.diagnosticProvider.clear();
}
}
let extension;
function activate(context) {
extension = new PlayWrongExtension();
extension.activate(context);
}
function deactivate() {
if (extension) {
extension.deactivate();
}
}
module.exports = {
activate,
deactivate
};

View File

@@ -0,0 +1,24 @@
{
"comments": {
"lineComment": "#",
"blockComment": ["/*", "*/"]
},
"brackets": [
["(", ")"],
["[", "]"],
["{", "}"],
["\"", "\""]
],
"autoClosingPairs": [
["(", ")"],
["[", "]"],
["{", "}"],
["\"", "\""]
],
"surroundingPairs": [
["(", ")"],
["[", "]"],
["{", "}"],
["\"", "\""]
]
}

View File

@@ -0,0 +1,7 @@
{
"name": "playwrong-syntax",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {}
}

View File

@@ -0,0 +1,67 @@
{
"name": "playwrong-syntax",
"displayName": "PlayWrong Test Language",
"description": "Syntax highlighting and linting for PlayWrong test files",
"version": "1.0.0",
"engines": {
"vscode": "^1.74.0"
},
"categories": [
"Programming Languages",
"Linters"
],
"main": "./extension.js",
"activationEvents": [
"onLanguage:playwrong"
],
"contributes": {
"languages": [
{
"id": "playwrong",
"aliases": ["PlayWrong", "playwrong"],
"extensions": [".test"],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "playwrong",
"scopeName": "source.playwrong",
"path": "./syntaxes/playwrong.tmLanguage.json"
}
],
"themes": [
{
"label": "PlayWrong Theme",
"uiTheme": "vs-dark",
"path": "./themes/playwrong-theme.json"
}
],
"commands": [
{
"command": "playwrong.lint",
"title": "Lint PlayWrong Test File",
"category": "PlayWrong"
},
{
"command": "playwrong.lintStrict",
"title": "Lint PlayWrong Test File (Strict)",
"category": "PlayWrong"
}
],
"menus": {
"editor/context": [
{
"when": "resourceLangId == playwrong",
"command": "playwrong.lint",
"group": "navigation"
},
{
"when": "resourceLangId == playwrong",
"command": "playwrong.lintStrict",
"group": "navigation"
}
]
}
}
}

View File

@@ -0,0 +1,120 @@
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "PlayWrong",
"patterns": [
{
"include": "#comments"
},
{
"include": "#strings"
},
{
"include": "#commands"
},
{
"include": "#numbers"
},
{
"include": "#variables"
}
],
"repository": {
"comments": {
"patterns": [
{
"name": "comment.line.number-sign.playwrong",
"begin": "#",
"end": "$",
"captures": {
"0": {
"name": "punctuation.definition.comment.playwrong"
}
},
"patterns": [
{
"name": "comment.line.number-sign.playwrong",
"match": "."
}
]
},
{
"name": "comment.block.playwrong",
"begin": "/\\*",
"end": "\\*/",
"captures": {
"0": {
"name": "punctuation.definition.comment.playwrong"
}
},
"patterns": [
{
"name": "comment.block.playwrong",
"match": "."
}
]
}
]
},
"commands": {
"patterns": [
{
"name": "keyword.control.playwrong",
"match": "\\b(use|open|wait|click|fill|scroll|sleep|dump|break)\\b"
},
{
"name": "entity.name.function.playwrong",
"match": "\\b(element|childText|class|id|name|href|type|value|child)\\b"
}
]
},
"strings": {
"patterns": [
{
"name": "string.quoted.double.playwrong",
"begin": "\"",
"end": "\"",
"beginCaptures": {
"0": {
"name": "punctuation.definition.string.begin.playwrong"
}
},
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.playwrong"
}
},
"patterns": [
{
"name": "constant.character.escape.playwrong",
"match": "\\\\."
},
{
"include": "#variables"
}
]
}
]
},
"numbers": {
"patterns": [
{
"name": "constant.numeric.playwrong",
"match": "\\b\\d+\\b"
}
]
},
"variables": {
"patterns": [
{
"name": "variable.other.playwrong",
"match": "\\$[A-Z_][A-Z0-9_]*"
},
{
"name": "variable.other.playwrong",
"match": "\\$\\{[A-Z_][A-Z0-9_]*\\}"
}
]
}
},
"scopeName": "source.playwrong"
}

View File

@@ -0,0 +1,75 @@
{
"name": "PlayWrong Theme",
"type": "dark",
"colors": {},
"tokenColors": [
{
"name": "PlayWrong Comments",
"scope": [
"comment.line.number-sign.playwrong",
"comment.block.playwrong",
"punctuation.definition.comment.playwrong"
],
"settings": {
"foreground": "#6A9955",
"fontStyle": "italic"
}
},
{
"name": "PlayWrong Strings",
"scope": [
"string.quoted.double.playwrong"
],
"settings": {
"foreground": "#CE9178"
}
},
{
"name": "PlayWrong String Punctuation",
"scope": [
"punctuation.definition.string.begin.playwrong",
"punctuation.definition.string.end.playwrong"
],
"settings": {
"foreground": "#CE9178"
}
},
{
"name": "PlayWrong Commands",
"scope": [
"keyword.control.playwrong"
],
"settings": {
"foreground": "#569CD6",
"fontStyle": "bold"
}
},
{
"name": "PlayWrong Parameters",
"scope": [
"entity.name.function.playwrong"
],
"settings": {
"foreground": "#DCDCAA"
}
},
{
"name": "PlayWrong Variables",
"scope": [
"variable.other.playwrong"
],
"settings": {
"foreground": "#9CDCFE"
}
},
{
"name": "PlayWrong Numbers",
"scope": [
"constant.numeric.playwrong"
],
"settings": {
"foreground": "#B5CEA8"
}
}
]
}

14
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Test Language CLI Headed",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/cli.js",
"args": ["step1.test", "Chrome", "--headed", "--screenshot-none"],
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"]
}
]
}

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"files.associations": {
"*.test": "playwrong"
}
}

73
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,73 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "npm install",
"type": "shell",
"command": "npm",
"args": ["install"],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "npm run test",
"type": "shell",
"command": "npm",
"args": ["run", "test"],
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "npm run test:headed",
"type": "shell",
"command": "npm",
"args": ["run", "test:headed"],
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "install browsers",
"type": "shell",
"command": "npm",
"args": ["run", "install-browsers"],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
},
{
"label": "install and run",
"dependsOrder": "sequence",
"dependsOn": ["npm install", "npm run test"],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
}
}
]
}