Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
extends: ['@eclipse-glsp'],
ignorePatterns: ['**/{node_modules,lib}', '**/.eslintrc.js'],
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.eslint.json'
},
settings: {
// Necessary for aliasing paths: https://www.typescriptlang.org/tsconfig#paths
'import/resolver': {
typescript: {
project: ['packages/glsp-playwright/tsconfig.json', 'tsconfig.json']
}
}
},
rules: {
'no-null/no-null': 'off', // Accessing the browser DOM returns "null" instead of "undefined"
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'sprotty',
message:
"The sprotty default exports are customized and reexported by GLSP. Please use '@eclipse-glsp/client' instead"
},
{
name: 'sprotty-protocol',
message:
"The sprotty-protocol default exports are customized and reexported by GLSP. Please use '@eclipse-glsp/client' instead"
}
],
patterns: ['../../../../*', '**/../index']
}
]
}
};
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
!.gitkeep

node_modules/
test-results/
playwright-report/
playwright/.cache/
lib/

examples/workflow-test/server/
examples/workflow-test/playwright/.storage/*.json

*.tsbuildinfo
*.log
*.jar
*.vsix
!*.env.example
*.env


1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"@eclipse-glsp/prettier-config"
12 changes: 12 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"DavidAnson.vscode-markdownlint"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}
55 changes: 55 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// If one would like to add/remove/modify user preferences without modifying the content of the
// workspace settings file, then one would need to modify the `settings.json` under here:
// - Windows: %APPDATA%\Code\User\settings.json
// - Linux: $HOME/.config/Code/User/settings.json
// - Mac: $HOME/Library/Application Support/Code/User/settings.json
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true
},
"eslint.validate": ["javascript", "typescript"],
"prettier.prettierPath": "node_modules/prettier",
"search.exclude": {
"**/node_modules": true,
"**/lib": true
},
"task.autoDetect": "off",
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.preferences.autoImportFileExcludePatterns": ["packages/*/src/index.ts"],
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"markdownlint.config": {
"MD007": {
"indent": 4
},
"MD030": {
"ul_single": 3,
"ul_multi": 3
}
}
}
55 changes: 55 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build all",
"detail": "Build all packages & examples",
"type": "shell",
"group": "build",
"command": "yarn",
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": ["$tsc", "$eslint-stylish"]
},
{
"label": "Watch all",
"detail": "Watch all packages & examples",
"type": "shell",
"group": "build",
"command": "yarn watch",
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": ["$tsc-watch"]
},
{
"label": "Download server",
"detail": "Download Workflow Diagram example server",
"type": "shell",
"group": "none",
"command": "cd examples/workflow-test && yarn download:server",
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Test standalone",
"detail": "Run test cases for standalone integration",
"type": "shell",
"group": "test",
"command": "cd examples/workflow-test && yarn test:standalone",
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
}
]
}
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,52 @@
# Eclipse GLSP - Playwright

We are planning to provide an end-to-end testing library for [GLSP diagrams](https://github.com/eclipse-glsp/glsp) based on [Playwright](https://playwright.dev).
A Playwright-based framework for testing the [Graphical Language Server Platform (GLSP)](https://github.com/eclipse-glsp/glsp).

## Structure

- `@eclipse-glsp/glsp-playwright`: Generic Playwright testing framework

## Building

This project is built with `yarn`.

## Workflow Diagram Example

The workflow diagram is a consistent example provided by all GLSP components. The example implements a simple flow chart diagram editor with different types of nodes and edges (see below).
The example can be used to try out different GLSP features, as well as several available integrations with IDE platforms (Theia, VSCode, Eclipse, Standalone).

The example test cases test the features provided by the GLSP client. The test cases in the [Workflow Example](https://github.com/eclipse-glsp/glsp-playwright/examples/workflow-test) demonstrate all supported features.

https://user-images.githubusercontent.com/588090/154459938-849ca684-11b3-472c-8a59-98ea6cb0b4c1.mp4

### How to test the Workflow Diagram example?

Clone this repository and build the packages:

```bash
yarn install
```

This command will also install Playwright and the necessary browsers.

Next, download a pre-built version of the Workflow Example diagram server by executing the task `Download server` or running the command:

```bash
cd examples/workflow-test
yarn download:server
```

Once the server has been downloaded, run the task `Test standalone` or the command `yarn test:standalone` in the root folder. Alternatively, the command `yarn test:standalone` in the `examples/workflow-test` folder can be also run.

### Tasks

The repository also provides build & watch tasks, so that you can build all packages with the task `Build all` or start watching all packages with `Watch all`.

## Documentation

We provide a [Documentation](./docs) for further information on the used concepts.

## More information

For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/).
If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/).
139 changes: 139 additions & 0 deletions docs/concepts/extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Extension

To provide the developers the possibility to write more accessible page objects and tests, we provide **Extensions**.

---

An **Extension** is a small isolated reusable unit that can provide new functionality to page objects. Those reusable units allow developers to add, for example, `click` functionality by reusing a default implementation provided by the framework.

## Categories

We differentiate between **Capabilities**, **Flows**, and **Models**.

### Capability

Capabilities provide GLSP-Client-specific functionality like accessing the `command-palette` or `popup`. Complex interaction possibilities with GLSP are defined there.

### Flow

Flows define an action or sequence of actions the user would typically do, like `clicking`, `hovering`, or `renaming` an element.

- The `Click` flow consists of only a single action, namely clicking on an element.
- The `Rename` flow consists of actions like double-clicking on the element, writing the new name, and pressing enter.

### Model

Models are mainly used to provide semantics to page objects. The framework sometimes requires further information from the page objects to enable better usability. For example, the `PLabelledElement` allows the page objects to define a label for an element. Afterward, elements based on those labels can be searched in the graph.

Capabilities and Flows provide default implementations most of the time; however, for **Models**, this is not always possible. Thus, making it necessary to use the interfaces directly.

## Mixin

The basis for **Extensions** are [Mixins](https://www.typescriptlang.org/docs/handbook/mixins.html). Mixins allow us to define the class hierarchy for the page objects dynamically. Due to this reason, it is possible to reuse functionality as necessary without polluting the prototype chain and to only use the necessary functionality in the page objects.

### Using Extensions

```ts
const TaskManualMixin = Mix(PNode)
.flow(useClickableFlow)
.flow(useHoverableFlow)
.flow(useDeletableFlow)
.capability(useResizeHandleCapability)
.capability(usePopupCapability)
.capability(useCommandPaletteCapability)
.build();
```

The code builds the class hierarchy for a page object. It can be read as follows. The root class is of class `PNode`. The class `PNode` is extended with `Clickable`, `Hoverable` and `Deletable` functionality. Finally, the capabilities `ResizeHandleCapability`, `PopupCapability`, and `CommandPaletteCapability` are added.

The result of this chaining is a new base class with all the functionality (e.g., `clicking`, `deleting`, accessing the `popup`) as listed. The `TaskManualMixin` can be again the base class for any other mixin or used as the base class for a page object.

```ts
export class TaskManual extends TaskManualMixin implements PLabelledElement {...}
```

**Models** can not be always used similary to **Capabilities** and **Flows**. In this case, the page object needs to implement the `PLabelledElement` interface directly and provide the necessary implementation. Afterward, the `TaskManual` can be used in places where the `PLabelledElement` is required.

### Defining new Extensions

**Capabilities** and **Flows** always consist of two necessary parts, namely the `Extension-Declaration` and `Extension-Provider`. **Models** have mostly only the `Extension-Declaration` part. No default implementation is available in this case, and the developers must provide it themselves.

#### Extension-Declaration

The `Extension-Declaration` interface defines the functionality the **Extension** wants to provide.

```ts
export interface PopupCapability<TPopup extends Popup = Popup> {
popup(): TPopup;
popupText(): Promise<string>;
}
```

The `Extension-Declaration` of the capability `Popup` is defined in the interface `PopupCapability`. It describes two methods, namely `popup()` and `popupText()`. The former returns the `popup` page object, and the latter the popup's text directly.

```ts
export interface Clickable {
click(): Promise<void>;
dblclick(): Promise<void>;
}
```

The `Extension-Declaration` of the flow `Clickable` defines two methods that trigger different click actions.

The framework uses those interfaces, and the final implementation is open to the users. They can reuse default implementations or provide their custom implementations. This approach allows the developers to override, restructure, or extend the functionality when necessary.

#### Extension-Provider

The `Extension-Provider` provides the default implementation for the specific `Extension-Declaration`.

```ts
export function usePopupCapability<TBase extends ConstructorA<Locateable & Hoverable>>(Base: TBase): Capability<TBase, PopupCapability> {
abstract class Mixin extends Base implements PopupCapability {
popup(): Popup {
return new Popup(this);
}

async popupText(): Promise<string> {
await this.hover();

return this.popup().innerText();
}
}

return Mixin;
}
```

The `Extension-Provider` is a function that returns the class implementing the interface of the `Extension-Declaration`. The function requires a base class to allow correct prototype chaining. Constraining the possible base class (e.g., `TBase extends ConstructorA<Locateable & Hoverable>`) is also possible. Only base classes that fulfill the specific constraint are allowed in this case. The implementation is up to the developers. The framework provides a default implementation.

Regardless, as the `provider` and the `declaration` is separated, it is possible to use completely new implementations without reusing the default `providers`. It is only necessary to define a new function that returns a class implementing the `Extension-Declaration` while respecting the class hierarchy and using it in the `Mix.flow` or `Mix.capability` methods.

Overriding only some aspects of a default `provider` is also possible. As the providers always return a class, the returned class from the `provider` could be used as the base class, as visible in the following snippet:

```ts
export function useCustomPopupCapability<TBase extends ConstructorA<Locateable & Hoverable>>(
Base: TBase
): Capability<TBase, PopupCapability> {
abstract class Mixin extends usePopupCapability(Base) implements PopupCapability {
override async popupText(): Promise<string> {
await this.hover();

return `Prefix: ${await this.popup().innerText()}`;
}
}

return Mixin;
}

const CustomTaskManualMixin = Mix(PNode)
.flow(useClickableFlow)
.flow(useHoverableFlow)
.flow(useDeletableFlow)
.capability(useResizeHandleCapability)
.capability(useCustomPopupCapability)
.capability(useCommandPaletteCapability)
.build();

// Or reuse
const CustomTaskManualMixin = Mix(TaskManualMixin).capability(useCustomPopupCapability).build();
```
Loading