Skip to content

Feature: Playwright executor #843

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
57 changes: 57 additions & 0 deletions executors/playwright/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Plawright Executor

The playwright executor allows you yo use venom to run Playwright tests
with the same yaml configuration file.

> NOTE: the playwright executor needs to use Playwright and as a result, will
> attempt to install playwright if it is not already installed.
> We use the [playwright-go](https://github.com/playwright-community/playwright-go) library for this

```yaml
name: Playwright testsuite
testcases:
- name: Check the title
steps:
- type: playwright
url: http://localhost:5173/
headless: true
actions:
- Fill "#email" "[email protected]"
- Fill "#email" "[email protected]"
- Fill "#password" "zikani123"
- Click "#loginButton"
- WaitFor ".second-dashboard-user-name"
assertions:
- result.page.body ShouldContainSubstring Parrot
- result.document.body ShouldContainSubstring Hello, Zikani
- result.document.body ShouldContainSubstring Logout
```


## Available actions

|Action|Arguments|Example|
|------|---------|-------|
|Click |**querySelector**| Click "#element" |
|DoubleClick |**querySelector**| DoubleClick "#element" |
|Tap |**querySelector**| Tap "#element" |
|Focus |**querySelector**| Focus "#element" |
|Blur |**querySelector**| Blur "#element" |
|Fill |**querySelector** TEXT| Fill "#element" "my input text" |
|Clear |**querySelector**| Clear "#element" |
|Check |**querySelector**| Check "#element" |
|Uncheck |**querySelector**| Uncheck "#element" |
|FillCheckbox |**querySelector**| FillCheckbox "#element" |
|Press |**querySelector** TEXT| Press "#element" "some text"|
|PressSequentially |**querySelector** TEXT | PressSequentially "#element" "some input"|
|Type |**querySelector** TEXT | Type "#element" |
|Select |**querySelector** TEXT | Select "#someSelect" "Value or Label"|
|SelectOption |**querySelector** TEXT | Select "#someSelect" "Value or Label"|
|SelectMultipleOptions |**querySelector** TEXT | SelectMultipleOptions "#someSelect" "Value or Label 1,Value or Label 2,..., Value or Label N"|
|WaitFor |**querySelector**| WaitFor "#element" |
|WaitForSelector |**querySelector**| WaitForSelector "#element" |
|Goto |**REGEX**| Goto "^some-page" |
|WaitForURL |**REGEX**| WaitForURL "^some-page" |
|GoBack |N/A| GoBack |
|GoForward |N/A| GoForward |
|Refresh |N/A| Refresh |
215 changes: 215 additions & 0 deletions executors/playwright/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package playwright

import (
"fmt"
"net/url"
"strings"

playwrightgo "github.com/playwright-community/playwright-go"
"github.com/spf13/cast"
)

type ActionFunc func(page playwrightgo.Page, element string, target ...any) error

var actionMap = map[string]ActionFunc{
"Click": ClickAction,
"DoubleClick": DoubleClickAction,
"Doubleclick": DoubleClickAction,
"Tap": TapAction,
"Focus": FocusAction,
"Blur": BlurAction,
"Clear": ClearAction,
"Fill": FillAction,
"Check": CheckAction,
"Uncheck": UncheckAction,
"FillCheckbox": CheckAction, // alias for Check
"Press": PressAction,
"PressSequentially": PressSequentiallyAction,
"Select": SelectOptionAction, // alias for SelectOption
"SelectOption": SelectOptionAction,
"SelectMultipleOptions": SelectMultipleOptionsAction,
"Type": PressSequentiallyAction, // alias for PressSequentially
"WaitFor": WaitForSelectorAction,
"WaitForSelector": WaitForSelectorAction,
"WaitForURL": WaitForURLAction,
"Goto": GotoAction,
"GoBack": GoBackAction,
"GoForward": GoForwardAction,
"Refresh": RefreshAction,
}

func removeQuotes(selector string) string {
return strings.TrimSuffix(strings.TrimPrefix(selector, `"`), `"`)
}

func ClickAction(page playwrightgo.Page, element string, target ...any) error {
return page.Locator(element).Click()
}

func WaitForSelectorAction(page playwrightgo.Page, selector string, target ...any) error {
timeout := 10_000.00
return page.Locator(selector).WaitFor(playwrightgo.LocatorWaitForOptions{
Timeout: &timeout,
State: playwrightgo.WaitForSelectorStateAttached,
})
}

func WaitForURLAction(page playwrightgo.Page, urlPattern string, target ...any) error {
timeout := 10_000.00
return page.WaitForURL(urlPattern, playwrightgo.PageWaitForURLOptions{
Timeout: &timeout,
WaitUntil: playwrightgo.WaitUntilStateCommit,
})
}

func FillAction(page playwrightgo.Page, element string, target ...any) error {
if target == nil {
return fmt.Errorf("need data to fill on '%s'", element)
}
return page.Locator(element).First().Fill(cast.ToString(target[0]))
}

func PressAction(page playwrightgo.Page, element string, target ...any) error {
if target == nil {
return fmt.Errorf("need key to press on '%s'", element)
}
return page.Locator(element).First().Press(cast.ToString(target[0]))
}

func PressSequentiallyAction(page playwrightgo.Page, element string, target ...any) error {
if target == nil {
return fmt.Errorf("need key to press on '%s'", element)
}
return page.Locator(element).First().PressSequentially(cast.ToString(target[0]))
}

func DoubleClickAction(page playwrightgo.Page, element string, target ...any) error {
if element == "" {
return fmt.Errorf("need element to double click on")
}
// TODO: support passing double click options
return page.Locator(element).First().Dblclick()
}

func TapAction(page playwrightgo.Page, element string, target ...any) error {
if element == "" {
return fmt.Errorf("need element to tap on")
}
// TODO: support passing Tap options
return page.Locator(element).First().Tap()
}

func FocusAction(page playwrightgo.Page, element string, target ...any) error {
if element == "" {
return fmt.Errorf("need element to focus on")
}
// TODO: support passing Focus options
return page.Locator(element).First().Focus()
}

func BlurAction(page playwrightgo.Page, element string, target ...any) error {
if element == "" {
return fmt.Errorf("need element to blur")
}
// TODO: support passing Blur options
return page.Locator(element).First().Blur()
}

func ClearAction(page playwrightgo.Page, element string, target ...any) error {
if element == "" {
return fmt.Errorf("need element to blur")
}
// TODO: support passing Clear options
return page.Locator(element).First().Clear()
}

func CheckAction(page playwrightgo.Page, element string, target ...any) error {
if element == "" {
return fmt.Errorf("need element to check on")
}
// TODO: support passing Check options
return page.Locator(element).First().Check()
}

func UncheckAction(page playwrightgo.Page, element string, target ...any) error {
if element == "" {
return fmt.Errorf("need element to uncheck on")
}
// TODO: support passing Uncheck options
return page.Locator(element).First().Uncheck()
}

func RefreshAction(page playwrightgo.Page, element string, target ...any) error {
_, err := page.Reload()
return err
}

func GoBackAction(page playwrightgo.Page, element string, target ...any) error {
_, err := page.GoBack()
return err
}

func GoForwardAction(page playwrightgo.Page, element string, target ...any) error {
_, err := page.GoForward()
return err
}

func GotoAction(page playwrightgo.Page, urlPattern string, target ...any) error {
timeout := 10_000.00
finalURL := urlPattern
if strings.HasPrefix(urlPattern, "/") { // relative url
parsedURL, err := url.Parse(page.URL())
if err != nil {
return err
}
u, err := parsedURL.Parse(urlPattern)
if err != nil {
return err
}
finalURL = u.String()
}
_, err := page.Goto(finalURL, playwrightgo.PageGotoOptions{
Timeout: &timeout,
WaitUntil: playwrightgo.WaitUntilStateCommit,
})
return err
}

func SelectOptionAction(page playwrightgo.Page, element string, target ...any) error {
if element == "" {
return fmt.Errorf("need a <select> element to select option on")
}
if target == nil || len(target) < 1 {
return fmt.Errorf("need a <select> element to select option on")
}
valuesOrLabels := make([]string, 0)
valuesOrLabels = append(valuesOrLabels, cast.ToString(target[0]))

_, err := page.Locator(element).First().SelectOption(playwrightgo.SelectOptionValues{
ValuesOrLabels: &valuesOrLabels,
})
return err
}

func SelectMultipleOptionsAction(page playwrightgo.Page, element string, target ...any) error {
if element == "" {
return fmt.Errorf("need a <select> element to select option on")
}
if target == nil || len(target) <= 1 {
return fmt.Errorf("need multiple elements to select from the element")
}
valuesOrLabels := make([]string, 0)
// typically target comes to us a single string, so we may need to treat it as
// a CSV to support selecting multiple options
for _, item := range strings.Split(cast.ToString(target[0]), ",") {
if item == "" {
return fmt.Errorf("need a <select> element to select option on")
}
valuesOrLabels = append(valuesOrLabels, cast.ToString(item))
}

_, err := page.Locator(element).First().SelectOption(playwrightgo.SelectOptionValues{
ValuesOrLabels: &valuesOrLabels,
})
return err
}
Loading