diff --git a/executors/playwright/README.md b/executors/playwright/README.md new file mode 100644 index 00000000..3452f894 --- /dev/null +++ b/executors/playwright/README.md @@ -0,0 +1,64 @@ +# 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: + - action: Fill + selector: "#email" + content: "change@example.com" + # you can write the expression in one line like this, if you want + - { action: Fill, selector: "#email", content: "zikani@example.com" } + - action: Fill + selector: "#password" + content: "zikani123" + - action: Click + selector: "#loginButton" + - action: WaitFor + selector: ".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 | diff --git a/executors/playwright/actions.go b/executors/playwright/actions.go new file mode 100644 index 00000000..cacd247e --- /dev/null +++ b/executors/playwright/actions.go @@ -0,0 +1,443 @@ +package playwright + +import ( + "encoding/json" + "fmt" + "net/url" + "os" + "strings" + + playwrightgo "github.com/playwright-community/playwright-go" + "github.com/spf13/cast" +) + +type ActionFunc func(page playwrightgo.Page, action *ExecutorAction) error + +var actionMap = map[string]ActionFunc{ + "click": ClickAction, + "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, + "screenshot": ScreenshotAction, + "upload": UploadFileAction, // alias for UploadFile + "uploadfile": UploadFileAction, + "uploadfiles": UploadMultipleFilesAction, // alias for UploadMultipleFiles + "uploadmultiplefiles": UploadMultipleFilesAction, +} + +func castOptions[Dest any](action *ExecutorAction) (dest *Dest, err error) { + data, err := json.Marshal(action.Options) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &dest) + if err != nil { + return nil, err + } + return dest, nil +} + +func ClickAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorClickOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorClickOptions") + } + return page.Locator(action.Selector).Click(*options) + } + return page.Locator(action.Selector).Click() +} + +func WaitForSelectorAction(page playwrightgo.Page, action *ExecutorAction) error { + timeout := 10_000.00 + defaultOptions := playwrightgo.LocatorWaitForOptions{ + Timeout: &timeout, + State: playwrightgo.WaitForSelectorStateAttached, + } + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorWaitForOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorWaitForOptions") + } + return page.Locator(action.Selector).WaitFor(*options) + } + return page.Locator(action.Selector).WaitFor(defaultOptions) +} + +func WaitForURLAction(page playwrightgo.Page, action *ExecutorAction) error { + timeout := 10_000.00 + defaultOptions := playwrightgo.PageWaitForURLOptions{ + Timeout: &timeout, + WaitUntil: playwrightgo.WaitUntilStateCommit, + } + + if action.Options != nil { + options, err := castOptions[playwrightgo.PageWaitForURLOptions](action) + if err != nil { + return fmt.Errorf("failed to parse PageWaitForURLOptions") + } + return page.WaitForURL(*options) + } + + urlPattern := action.Selector + return page.WaitForURL(urlPattern, defaultOptions) +} + +func FillAction(page playwrightgo.Page, action *ExecutorAction) error { + element := action.Selector + target := action.Content + if target == "" { + return fmt.Errorf("need data to fill on '%s'", element) + } + + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorFillOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorFillOptions") + } + return page.Locator(element).First().Fill(cast.ToString(target), *options) + } + return page.Locator(element).First().Fill(cast.ToString(target)) +} + +func PressAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Content == "" { + return fmt.Errorf("need key to press on '%s'", action.Selector) + } + + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorPressOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorPressOptions") + } + return page.Locator(action.Selector).First().Press(cast.ToString(action.Content), *options) + } + return page.Locator(action.Selector).First().Press(cast.ToString(action.Content)) +} + +func PressSequentiallyAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Content == "" { + return fmt.Errorf("need key to press on '%s'", action.Selector) + } + + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorPressSequentiallyOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorPressSequentiallyOptions") + } + return page.Locator(action.Selector).First().PressSequentially(cast.ToString(action.Content), *options) + } + return page.Locator(action.Selector).First().PressSequentially(cast.ToString(action.Content)) +} + +func DoubleClickAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Selector == "" { + return fmt.Errorf("need element to double click on") + } + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorDblclickOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorDblclickOptions") + } + return page.Locator(action.Selector).First().Dblclick(*options) + } + return page.Locator(action.Selector).First().Dblclick() +} + +func TapAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Selector == "" { + return fmt.Errorf("need element to tap on") + } + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorTapOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorTapOptions") + } + return page.Locator(action.Selector).First().Tap(*options) + } + return page.Locator(action.Selector).First().Tap() +} + +func FocusAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Selector == "" { + return fmt.Errorf("need element to focus on") + } + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorFocusOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorFocusOptions") + } + return page.Locator(action.Selector).First().Focus(*options) + } + return page.Locator(action.Selector).First().Focus() +} + +func BlurAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Selector == "" { + return fmt.Errorf("need element to blur") + } + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorBlurOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorBlurOptions") + } + return page.Locator(action.Selector).First().Blur(*options) + } + return page.Locator(action.Selector).First().Blur() +} + +func ClearAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Selector == "" { + return fmt.Errorf("need element to blur") + } + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorClearOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorClearOptions") + } + return page.Locator(action.Selector).First().Clear(*options) + } + return page.Locator(action.Selector).First().Clear() +} + +func CheckAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Selector == "" { + return fmt.Errorf("need element to check on") + } + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorCheckOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorCheckOptions") + } + return page.Locator(action.Selector).First().Check(*options) + } + return page.Locator(action.Selector).First().Check() +} + +func UncheckAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Selector == "" { + return fmt.Errorf("need element to uncheck on") + } + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorUncheckOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorUncheckOptions") + } + return page.Locator(action.Selector).First().Uncheck(*options) + } + return page.Locator(action.Selector).First().Uncheck() +} + +func RefreshAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Options != nil { + options, err := castOptions[playwrightgo.PageReloadOptions](action) + if err != nil { + return fmt.Errorf("failed to parse PageReloadOptions") + } + _, err = page.Reload(*options) + return err + } + _, err := page.Reload() + return err +} + +func GoBackAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Options != nil { + options, err := castOptions[playwrightgo.PageGoBackOptions](action) + if err != nil { + return fmt.Errorf("failed to parse PageGoBackOptions") + } + _, err = page.GoBack(*options) + return err + } + _, err := page.GoBack() + return err +} + +func GoForwardAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Options != nil { + options, err := castOptions[playwrightgo.PageGoForwardOptions](action) + if err != nil { + return fmt.Errorf("failed to parse PageGoForwardOptions") + } + _, err = page.GoForward(*options) + return err + } + _, err := page.GoForward() + return err +} + +func GotoAction(page playwrightgo.Page, action *ExecutorAction) error { + urlPattern := action.Selector + timeout := 10_000.00 + defaultOptions := playwrightgo.PageGotoOptions{ + Timeout: &timeout, + WaitUntil: playwrightgo.WaitUntilStateCommit, + } + + options := defaultOptions + if action.Options != nil { + userOpts, err := castOptions[playwrightgo.PageGotoOptions](action) + if err != nil { + return fmt.Errorf("failed to parse PageGotoOptions") + } + options = *userOpts + } + + 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, options) + return err +} + +func SelectOptionAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Selector == "" { + return fmt.Errorf("need a element to select option on") + } + valuesOrLabels := make([]string, 0) + valuesOrLabels = append(valuesOrLabels, cast.ToString(action.Content)) + + selectOptionValues := playwrightgo.SelectOptionValues{ + ValuesOrLabels: &valuesOrLabels, + } + + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorSelectOptionOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorSelectOptionOptions") + } + _, err = page.Locator(action.Selector).First().SelectOption(selectOptionValues, *options) + return err + } + + _, err := page.Locator(action.Selector).First().SelectOption(selectOptionValues) + return err +} + +func SelectMultipleOptionsAction(page playwrightgo.Page, action *ExecutorAction) error { + if action.Selector == "" { + return fmt.Errorf("need a element to select option on") + } + valuesOrLabels = append(valuesOrLabels, cast.ToString(item)) + } + + selectOptionValues := playwrightgo.SelectOptionValues{ + ValuesOrLabels: &valuesOrLabels, + } + + if action.Options != nil { + options, err := castOptions[playwrightgo.LocatorSelectOptionOptions](action) + if err != nil { + return fmt.Errorf("failed to parse LocatorSelectOptionOptions") + } + _, err = page.Locator(action.Selector).First().SelectOption(selectOptionValues, *options) + return err + } + + _, err := page.Locator(action.Selector).First().SelectOption(selectOptionValues) + return err +} + +func ScreenshotAction(page playwrightgo.Page, action *ExecutorAction) error { + opts, err := castOptions[playwrightgo.PageScreenshotOptions](action) + if err != nil { + return err + } + defaultTimeout := 10_000.00 + fullPage := true + caretOption := playwrightgo.ScreenshotCaret("hide") + defaultOpts := playwrightgo.PageScreenshotOptions{ + Caret: &caretOption, + FullPage: &fullPage, + Timeout: &defaultTimeout, + } + if opts == nil { + opts = &defaultOpts + } + screenshotBytes, err := page.Screenshot(*opts) + if err != nil { + return err + } + err = os.WriteFile(action.Content, screenshotBytes, 0o775) + return err +} + +func UploadFileAction(page playwrightgo.Page, action *ExecutorAction) error { + // - files should be one of: string, []string, InputFile, []InputFile, + // string: local file path + filename := action.Content + noWaitAfter := true + timeout := 10_000.00 + opts, err := castOptions[playwrightgo.LocatorSetInputFilesOptions](action) + if err != nil { + return err + } + defaultOpts := playwrightgo.LocatorSetInputFilesOptions{ + NoWaitAfter: &noWaitAfter, + Timeout: &timeout, + } + if opts == nil { + opts = &defaultOpts + } + return page.Locator(action.Selector).First().SetInputFiles(filename, *opts) +} + +func UploadMultipleFilesAction(page playwrightgo.Page, action *ExecutorAction) error { + files := action.Content + noWaitAfter := true + timeout := 10_000.00 + opts, err := castOptions[playwrightgo.LocatorSetInputFilesOptions](action) + if err != nil { + return err + } + defaultOpts := playwrightgo.LocatorSetInputFilesOptions{ + NoWaitAfter: &noWaitAfter, + Timeout: &timeout, + } + if opts == nil { + opts = &defaultOpts + } + return page.Locator(action.Selector).First().SetInputFiles(files, *opts) +} diff --git a/executors/playwright/playwright.go b/executors/playwright/playwright.go new file mode 100644 index 00000000..8a696535 --- /dev/null +++ b/executors/playwright/playwright.go @@ -0,0 +1,176 @@ +package playwright + +import ( + "context" + "fmt" + "net/url" + "slices" + "strings" + + "github.com/mitchellh/mapstructure" + "github.com/ovh/venom" + playwrightgo "github.com/playwright-community/playwright-go" +) + +const Name = "playwright" + +type Executor struct { + URL string `json:"url" yaml:"url"` + Browser string `json:"browser" yaml:"browser"` + Actions []ExecutorAction `json:"actions" yaml:"actions"` + Headless bool `json:"headless" yaml:"headless"` +} + +type ExecutorAction struct { + Action string `json:"action"` // The action to perform, must be a valid/supported action + Selector string `json:"selector" yaml:"selector"` // DOM selector or expression + Content string `json:"content,omitempty" yaml:"content"` // Content for actions that require it + Options any `json:"options,omitempty" yaml:"options"` // Options applicable to the given action +} + +func (a ExecutorAction) String() string { + return fmt.Sprintf("action: %s selector: %s content: %s, options: %v", a.Action, a.Selector, a.Content, a.Options) +} + +func New() venom.Executor { + return &Executor{ + Headless: true, + } +} + +type Result struct { + Page *Page `json:"page" yaml:"page"` + Document *Page `json:"document" yaml:"document"` // alias to Page +} + +type Page struct { + Location *url.URL `json:"location" yaml:"location"` + Body string `json:"body" yaml:"body"` + Query *PageQuery `json:"query" yaml:"query"` + Scripts []string `json:"scripts" yaml:"scripts"` + CSSFiles []string `json:"css_files" yaml:"css_files"` +} + +// PageQuery allows users to assert the page.Body using css selectrors +type PageQuery struct { +} + +// ZeroValueResult return an empty implementation of this executor result +func (Executor) ZeroValueResult() interface{} { + return Result{} +} + +// GetDefaultAssertions return default assertions for type exec +func (Executor) GetDefaultAssertions() *venom.StepAssertions { + return &venom.StepAssertions{Assertions: []venom.Assertion{"page.body ShouldNotBeEmpty"}} +} + +// Run execute TestStep of type playwright +func (Executor) Run(ctx context.Context, step venom.TestStep) (interface{}, error) { + var e Executor + if err := mapstructure.Decode(step, &e); err != nil { + return nil, err + } + + pageURL, err := url.Parse(e.URL) + if err != nil { + return nil, fmt.Errorf("invalid URL passed to playright executor: %s", e.URL) + } + + browsers := make([]string, 0) + if e.Browser != "" && slices.Contains[[]string, string]([]string{"chromium", "firefox"}, e.Browser) { + browsers = append(browsers, e.Browser) + } else { + browsers = append(browsers, "chromium") + } + err = playwrightgo.Install(&playwrightgo.RunOptions{ + Browsers: browsers, + }) + if err != nil { + return nil, fmt.Errorf("could not launch playwright: %w", err) + } + + pw, err := playwrightgo.Run() + if err != nil { + return nil, fmt.Errorf("could not launch playwright: %w", err) + } + browser, err := pw.Chromium.Launch(playwrightgo.BrowserTypeLaunchOptions{ + Headless: playwrightgo.Bool(e.Headless), // should we expose this option? + }) + if err != nil { + return nil, fmt.Errorf("could not launch Chromium: %w", err) + } + context, err := browser.NewContext() + if err != nil { + return nil, fmt.Errorf("could not create context: %w", err) + } + page, err := context.NewPage() + if err != nil { + return nil, fmt.Errorf("could not create page: %w", err) + } + + _, err = page.Goto(e.URL) + if err != nil { + return nil, fmt.Errorf("could not goto: %w", err) + } + + err = performActions(ctx, page, e.Actions) + if err != nil { + return nil, err + } + + pageBodyBytes, err := page.Content() + if err != nil { + return nil, fmt.Errorf("could not goto: %w", err) + } + + err = browser.Close() + if err != nil { + return nil, fmt.Errorf("could not close browser: %w", err) + } + err = pw.Stop() + if err != nil { + return nil, fmt.Errorf("could not stop Playwright: %w", err) + } + + pageResult := &Page{ + Location: pageURL, + Body: string(pageBodyBytes), + Query: nil, + } + + return Result{ + Page: pageResult, + Document: pageResult, + }, nil +} + +func performActions(ctx context.Context, page playwrightgo.Page, actions []ExecutorAction) error { + for _, action := range actions { + if action.Action == "" { + return fmt.Errorf("action cannot be empty, please specify an action") + } + if action.Selector == "" { + return fmt.Errorf("selector cannot be empty, please specify a selector") + } + + actionName := strings.ToLower(action.Action) + actionFunc, ok := actionMap[actionName] + if !ok { + return fmt.Errorf("invalid or unsupported action: '%s'", actionName) + } + + venom.Debug(ctx, fmt.Sprintf("performing action '%s'", action)) + + var actErr error + if len(action.Content) <= 1 { + actErr = actionFunc(page, &action) + } else { + actErr = actionFunc(page, &action) + } + if actErr != nil { + return actErr + } + } + return nil +} diff --git a/executors/playwright/playwright_test.go b/executors/playwright/playwright_test.go new file mode 100644 index 00000000..56cfac79 --- /dev/null +++ b/executors/playwright/playwright_test.go @@ -0,0 +1,105 @@ +package playwright + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/ovh/venom" + playwrightgo "github.com/playwright-community/playwright-go" +) + +const testPage = ` + + + + + + + Test Page + + + +
+

Example form

+
+
+
+
+
+ +
+ + + + + +` + +func TestPerformActions(t *testing.T) { + venom.InitTestLogger(t) + + testActions := []ExecutorAction{ + {Action: "Fill", Selector: "#firstName", Content: "John"}, + {Action: "Fill", Selector: "[name=lastName]", Content: "John"}, + {Action: "Fill", Selector: "#age", Content: "24"}, + {Action: "Focus", Selector: "#email"}, + {Action: "WaitFor", Selector: "#inputted-age", Content: "24"}, + {Action: "Click", Selector: "#submit-button"}, + } + + pw, err := playwrightgo.Run() + if err != nil { + t.Fail() + } + browser, err := pw.Chromium.Launch(playwrightgo.BrowserTypeLaunchOptions{ + Headless: playwrightgo.Bool(true), + }) + if err != nil { + t.Fail() + } + browserCtx, err := browser.NewContext() + if err != nil { + t.Fail() + } + page, err := browserCtx.NewPage() + if err != nil { + t.Fail() + } + + err = page.SetContent(testPage, playwrightgo.PageSetContentOptions{}) + if err != nil { + t.Error("failed to set testPage content") + } + + err = performActions(context.Background(), page, testActions) + if err != nil { + t.Errorf("failed to test actions %v", err) + } + + t.Cleanup(func() { + err = browser.Close() + if err != nil { + fmt.Fprintf(os.Stderr, "failed to close browser properly %v", err) + } + err = pw.Stop() + if err != nil { + fmt.Fprintf(os.Stderr, "failed to close browser properly %v", err) + } + }) +} diff --git a/executors/registry.go b/executors/registry.go index 851b40b0..6388a551 100644 --- a/executors/registry.go +++ b/executors/registry.go @@ -12,6 +12,7 @@ import ( "github.com/ovh/venom/executors/mongo" "github.com/ovh/venom/executors/mqtt" "github.com/ovh/venom/executors/ovhapi" + "github.com/ovh/venom/executors/playwright" "github.com/ovh/venom/executors/rabbitmq" "github.com/ovh/venom/executors/readfile" "github.com/ovh/venom/executors/redis" @@ -34,6 +35,7 @@ var Registry map[string]Constructor = map[string]Constructor{ kafka.Name: kafka.New, mqtt.Name: mqtt.New, ovhapi.Name: ovhapi.New, + playwright.Name: playwright.New, rabbitmq.Name: rabbitmq.New, readfile.Name: readfile.New, redis.Name: redis.New, diff --git a/go.mod b/go.mod index 4e37505a..409843be 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/ovh/venom -go 1.21 +go 1.22 + +toolchain go1.24.1 require ( github.com/Azure/go-amqp v1.0.2 @@ -33,6 +35,7 @@ require ( github.com/ovh/cds/sdk/interpolate v0.0.0-20231019155847-e738a974db8f github.com/ovh/go-ovh v1.4.3 github.com/pkg/errors v0.9.1 + github.com/playwright-community/playwright-go v0.5101.0 github.com/rockbears/yaml v0.4.0 github.com/rubenv/sql-migrate v1.5.2 github.com/sijms/go-ora v1.3.2 @@ -53,6 +56,12 @@ require ( modernc.org/sqlite v1.26.0 ) +require ( + github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-stack/stack v1.8.1 // indirect +) + require ( github.com/ClickHouse/ch-go v0.55.0 // indirect github.com/ClickHouse/clickhouse-go/v2 v2.9.1 // indirect diff --git a/go.sum b/go.sum index dbe9a992..f5238a17 100644 --- a/go.sum +++ b/go.sum @@ -658,6 +658,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= @@ -716,6 +718,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -726,6 +730,8 @@ github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY= github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= @@ -990,6 +996,8 @@ github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcs github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b h1:Ga1nclDSe8gOw37MVLMhfu2QKWtD6gvtQ298zsKVh8g= @@ -1020,6 +1028,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/playwright-community/playwright-go v0.5101.0 h1:gVCMZThDO76LJ/aCI27lpB8hEAWhZszeS0YB+oTxJp0= +github.com/playwright-community/playwright-go v0.5101.0/go.mod h1:kBNWs/w2aJ2ZUp1wEOOFLXgOqvppFngM5OS+qyhl+ZM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= @@ -1038,8 +1048,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1144,6 +1154,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1265,6 +1276,7 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1394,6 +1406,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1404,6 +1418,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1422,6 +1438,7 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/tests/playwright.yml b/tests/playwright.yml new file mode 100644 index 00000000..100ac553 --- /dev/null +++ b/tests/playwright.yml @@ -0,0 +1,17 @@ +name: Playwright testsuite +testcases: + - name: testA + steps: + - type: playwright + url: http://localhost:5173/ + headless: true + actions: + - Fill "#email" "change@example.com" + - Fill "#email" "zikani@example.com" + - 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