diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5d95bd2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,64 @@ +name: CI + +on: + push: + branches: [ master, main ] + +jobs: + set-deps-dotnet-poop-here: + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/dotnet/sdk:6.0 + outputs: + semVer: ${{ steps.gitversion.outputs.semVer }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v0.9.15 + with: + versionSpec: '5.x' + - name: Set SemVer Version + uses: gittools/actions/gitversion/execute@v0.9.15 + id: gitversion + + - name: echo VERSIONS + run: | + echo "REVISION -> $GITHUB_SHA" + echo "VERSION -> $GITVERSION_SEMVER" + test: + runs-on: ubuntu-latest + container: + image: golang:1.19-bullseye + needs: set-deps-dotnet-poop-here + env: + SEMVER: ${{ needs.set-deps-dotnet-poop-here.outputs.semVer }} + steps: + - uses: actions/checkout@v3 + - name: install deps + run: | + apt update && apt install jq -y + make REVISION=$GITHUB_SHA install + - name: make test + run: | + make REVISION=$GITHUB_SHA test + - name: Publish Junit style Test Report + uses: mikepenz/action-junit-report@v3 + if: always() # always run even if the previous step fails + with: + report_paths: '**/report-junit.xml' + - name: Analyze with SonarCloud + # You can pin the exact commit or the version. + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret) + with: + # Additional arguments for the sonarcloud scanner + args: + # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu) + # mandatory + -Dsonar.projectVersion=$SEMVER + -Dsonar.go.coverage.reportPaths=/github/workspace/.coverage/out + -X diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..e10f92d --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,64 @@ +name: PR + +on: + pull_request: + branches: [ master, main ] + +jobs: + set-deps-dotnet-poop-here: + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/dotnet/sdk:6.0 + outputs: + semVer: ${{ steps.gitversion.outputs.semVer }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v0.9.15 + with: + versionSpec: '5.x' + - name: Set SemVer Version + uses: gittools/actions/gitversion/execute@v0.9.15 + id: gitversion + + - name: echo VERSIONS + run: | + echo "REVISION -> $GITHUB_SHA" + echo "VERSION -> $GITVERSION_SEMVER" + pr: + runs-on: ubuntu-latest + container: + image: golang:1.19-bullseye + needs: set-deps-dotnet-poop-here + env: + REVISION: $GITHUB_SHA + SEMVER: ${{ needs.set-deps-dotnet-poop-here.outputs.semVer }} + steps: + - uses: actions/checkout@v3 + - name: install deps + run: | + apt update && apt install jq -y + make REVISION=$GITHUB_SHA install + - name: make test + run: | + make REVISION=$GITHUB_SHA test + - name: Publish Junit style Test Report + uses: mikepenz/action-junit-report@v3 + if: always() # always run even if the previous step fails + with: + report_paths: '**/report-junit.xml' + - name: Analyze with SonarCloud + # You can pin the exact commit or the version. + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret) + with: + # Additional arguments for the sonarcloud scanner + args: + # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu) + # mandatory + -Dsonar.projectVersion=$SEMVER + -Dsonar.go.coverage.reportPaths=/github/workspace/.coverage/out diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..09ba463 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,53 @@ +name: release + +on: + workflow_run: + workflows: ['CI'] + types: + - completed + +jobs: + set-deps-dotnet-poop-here: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + container: + image: mcr.microsoft.com/dotnet/sdk:6.0 + outputs: + semVer: ${{ steps.gitversion.outputs.semVer }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v0.9.15 + with: + versionSpec: '5.x' + - name: Set SemVer Version + uses: gittools/actions/gitversion/execute@v0.9.15 + id: gitversion + + - name: echo VERSIONS + run: | + echo "REVISION -> $GITHUB_SHA" + echo "VERSION -> $GITVERSION_SEMVER" + release: + runs-on: ubuntu-latest + container: + image: golang:1.19-bullseye + env: + FOO: Bar + needs: set-deps-dotnet-poop-here + env: + SEMVER: ${{ needs.set-deps-dotnet-poop-here.outputs.semVer }} + steps: + - uses: actions/checkout@v3 + - name: install deps + run: | + apt update && apt install jq -y + make REVISION=$GITHUB_SHA install + - name: release library + run: make GIT_TAG=${SEMVER} REVISION=$GITHUB_SHA tag + - name: release binary + run: | + make REVISION=$GITHUB_SHA GIT_TAG=${SEMVER} PAT=${{ secrets.GITHUB_TOKEN }} build + make REVISION=$GITHUB_SHA GIT_TAG=${SEMVER} PAT=${{ secrets.GITHUB_TOKEN }} release diff --git a/.gitignore b/.gitignore index dd4fc3a..4bfaae9 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ dist/ # test .coverage +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1b63724 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM debian:bullseye + +WORKDIR /seeder + +COPY dist/uiseeder-linux uiseeder +COPY .ignore-paypal.yml paypal.yml + +# RUN apt-get update \ +# && apt install -y libnss3 + +ENTRYPOINT [ "/seeder/uiseeder", "-i", "/seeder/paypal.yml" ] \ No newline at end of file diff --git a/Makefile b/Makefile index 571228d..2e634d6 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ clean: bingen: for os in darwin linux windows; do \ - GOOS=$$os CGO_ENABLED=0 go build -a -tags netgo -installsuffix netgo $(LDFLAGS) -o dist/uiseeder-$$os ./cmd; \ + GOOS=$$os CGO_ENABLED=0 go build -mod=readonly -a -tags netgo -installsuffix netgo $(LDFLAGS) -o dist/uiseeder-$$os ./cmd; \ done build: clean install bingen @@ -30,8 +30,8 @@ build: clean install bingen build_ci: clean install_ci bingen tag: - git tag "v$(GIT_TAG)" - git push origin "v$(GIT_TAG)" + git tag -a $(VERSION) -m "ci tag release uistrategy" $(REVISION) + git push origin $(VERSION) release: OWNER=$(OWNER) NAME=$(NAME) PAT=$(PAT) VERSION=$(VERSION) . hack/release.sh @@ -42,7 +42,7 @@ btr: build tag release # TEST test: test_prereq - go test `go list ./... | grep -v */generated/` -v -mod=readonly -coverprofile=.coverage/out | go-junit-report > .coverage/report-junit.xml && \ + go test `go list ./... | grep -v */generated/` -v -mod=readonly -race -coverprofile=.coverage/out | go-junit-report > .coverage/report-junit.xml && \ gocov convert .coverage/out | gocov-xml > .coverage/report-cobertura.xml test_ci: @@ -52,4 +52,4 @@ test_prereq: mkdir -p .coverage go install github.com/jstemmer/go-junit-report/v2@latest && \ go install github.com/axw/gocov/gocov@latest && \ - go install github.com/AlekSi/gocov-xml@latest \ No newline at end of file + go install github.com/AlekSi/gocov-xml@latest diff --git a/internal/cmdutil/cmdutil_test.go b/internal/cmdutil/cmdutil_test.go index c203dc9..a3c7ec3 100644 --- a/internal/cmdutil/cmdutil_test.go +++ b/internal/cmdutil/cmdutil_test.go @@ -1,22 +1,30 @@ -package cmdutil +package cmdutil_test import ( "bytes" "reflect" "testing" + "github.com/dnitsch/configmanager/pkg/generator" "github.com/dnitsch/uistrategy" + "github.com/dnitsch/uistrategy/internal/cmdutil" "github.com/dnitsch/uistrategy/internal/util" ) -func Test_YamlParse(t *testing.T) { - tests := []struct { +type mockConfManger func(input string, config generator.GenVarsConfig) (string, error) + +func (m mockConfManger) RetrieveWithInputReplaced(input string, config generator.GenVarsConfig) (string, error) { + return m(input, config) +} + +func TestYamlParse(t *testing.T) { + tests := map[string]struct { name string confContents []byte + mockConfMgr func(t *testing.T) mockConfManger expect uistrategy.UiStrategyConf }{ - { - name: "all config items", + "all config items": { confContents: []byte(` setup: baseUrl: http://127.0.0.1:8090 @@ -24,23 +32,20 @@ setup: auth: navigate: /_/#/login username: - must: true value: test@example.com - xPath: //*[@class="app-body"]/div[1]/main/div/form/div[2]/input + selector: //*[@class="app-body"]/div[1]/main/div/form/div[2]/input password: - must: true value: P4s$w0rd123! - xPath: //*[@class="app-body"]/div[1]/main/div/form/div[3]/input + selector: //*[@class="app-body"]/div[1]/main/div/form/div[3]/input submit: - must: true - cssSelector: '#app > div > div > div.page-wrapper.full-page.center-content > main > div > form > button' + selector: '#app > div > div > div.page-wrapper.full-page.center-content > main > div > form > button' actions: - name: create test collection navigate: /_/?#/collections elementActions: - name: Ceate new collection element: - cssSelector: '#app > div > div > div.page-wrapper.center-content > main > div > button'`), + selector: '#app > div > div > div.page-wrapper.center-content > main > div > button'`), expect: uistrategy.UiStrategyConf{ Setup: uistrategy.BaseConfig{ BaseUrl: "http://127.0.0.1:8090", @@ -52,26 +57,23 @@ actions: Username: uistrategy.Element{ Value: util.Str("test@example.com"), Timeout: 0, - Must: true, Selector: util.Str(`//*[@class="app-body"]/div[1]/main/div/form/div[2]/input`), }, Password: uistrategy.Element{ Value: util.Str("P4s$w0rd123!"), Timeout: 0, - Must: true, Selector: util.Str(`//*[@class="app-body"]/div[1]/main/div/form/div[3]/input`), }, Submit: uistrategy.Element{ - Must: true, Value: nil, Timeout: 0, Selector: util.Str(`#app > div > div > div.page-wrapper.full-page.center-content > main > div > form > button`), }, }, - Actions: []uistrategy.ViewAction{{ + Actions: []*uistrategy.ViewAction{{ Name: "create test collection", Navigate: "/_/?#/collections", - ElementActions: []uistrategy.ElementAction{{ + ElementActions: []*uistrategy.ElementAction{{ Name: "Ceate new collection", Element: uistrategy.Element{ Selector: util.Str(`#app > div > div > div.page-wrapper.center-content > main > div > button`), @@ -83,9 +85,33 @@ actions: }, }, }, + mockConfMgr: func(t *testing.T) mockConfManger { + return mockConfManger(func(input string, config generator.GenVarsConfig) (string, error) { + return ` +setup: + baseUrl: http://127.0.0.1:8090 + continueOnError: true +auth: + navigate: /_/#/login + username: + value: test@example.com + selector: //*[@class="app-body"]/div[1]/main/div/form/div[2]/input + password: + value: P4s$w0rd123! + selector: //*[@class="app-body"]/div[1]/main/div/form/div[3]/input + submit: + selector: '#app > div > div > div.page-wrapper.full-page.center-content > main > div > form > button' +actions: +- name: create test collection + navigate: /_/?#/collections + elementActions: + - name: Ceate new collection + element: + selector: '#app > div > div > div.page-wrapper.center-content > main > div > button'`, nil + }) + }, }, - { - name: "no auth provided", + "no auth provided": { confContents: []byte(` setup: baseUrl: http://127.0.0.1:8090 @@ -95,7 +121,7 @@ actions: elementActions: - name: Click MarketPlace element: - cssSelector: 'body > div.application-main > main > div.MarketplaceHeader.pt-6.pt-lg-10.position-relative.color-bg-default > div.container-lg.p-responsive.text-center.text-md-left > div > div > a' + selector: 'body > div.application-main > main > div.MarketplaceHeader.pt-6.pt-lg-10.position-relative.color-bg-default > div.container-lg.p-responsive.text-center.text-md-left > div > div > a' `), expect: uistrategy.UiStrategyConf{ Setup: uistrategy.BaseConfig{ @@ -104,11 +130,11 @@ actions: LauncherConfig: nil, }, Auth: nil, - Actions: []uistrategy.ViewAction{ + Actions: []*uistrategy.ViewAction{ { Name: "create new marketplace app", Navigate: "/marketplace", - ElementActions: []uistrategy.ElementAction{{ + ElementActions: []*uistrategy.ElementAction{{ Name: "Click MarketPlace", Element: uistrategy.Element{ Selector: util.Str(`body > div.application-main > main > div.MarketplaceHeader.pt-6.pt-lg-10.position-relative.color-bg-default > div.container-lg.p-responsive.text-center.text-md-left > div > div > a`), @@ -120,15 +146,30 @@ actions: }, }, }, + mockConfMgr: func(t *testing.T) mockConfManger { + return mockConfManger(func(input string, config generator.GenVarsConfig) (string, error) { + return ` +setup: + baseUrl: http://127.0.0.1:8090 +actions: + - name: create new marketplace app + navigate: /marketplace + elementActions: + - name: Click MarketPlace + element: + selector: 'body > div.application-main > main > div.MarketplaceHeader.pt-6.pt-lg-10.position-relative.color-bg-default > div.container-lg.p-responsive.text-center.text-md-left > div > div > a' +`, nil + }) + }, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for name, tt := range tests { + t.Run(name, func(t *testing.T) { conf := &uistrategy.UiStrategyConf{} - err := YamlParseInput(conf, bytes.NewReader(tt.confContents)) + err := cmdutil.YamlParseInput(conf, bytes.NewReader(tt.confContents), tt.mockConfMgr(t)) if err != nil { - t.Error(" error parsing") + t.Fatalf("failed to parse %q with err: %v", tt.confContents, err.Error()) } if !reflect.DeepEqual(conf.Setup, tt.expect.Setup) { diff --git a/report.go b/report.go index b5b4d23..e766e81 100644 --- a/report.go +++ b/report.go @@ -5,30 +5,29 @@ import ( "os" ) -func (web *Web) buildReport(allActions []ViewAction) { +func (web *Web) buildReport(allActions []*ViewAction) { - vrs := []ViewReport{} + vrs := make(ViewReport) for _, v := range allActions { - vr := ViewReport{ - Name: v.Name, + actions := make(ActionsReport) + vrs[v.Name] = ViewReportItem{ Message: v.message, + Actions: actions, } - for _, a := range v.ElementActions { - va := ActionReport{ - Name: a.Name, - Message: a.message, - Screenshot: a.screenshot, - Errored: a.errored, + for _, ap := range v.ElementActions { + vrs[v.Name].Actions[ap.Name] = ActionReportItem{ + Message: ap.message, + Screenshot: ap.screenshot, + Errored: ap.errored, + Output: ap.capturedOutput, } - vr.Actions = append(vr.Actions, va) } - vrs = append(vrs, vr) } web.flushReport(vrs) } -func (web *Web) flushReport(report []ViewReport) error { +func (web *Web) flushReport(report ViewReport) error { file := `.report/report.json` w, err := os.OpenFile(file, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..d7e2ffd --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,15 @@ +sonar.projectKey=dnitsch_uistrategy +sonar.organization=dnitsch +sonar.projectName=uistrategy +# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. +sonar.sources=. +sonar.exclusions=**/*_test.go,**/*_generated*.go,**/*_generated/**,**/vendor/**,**/examples/** +sonar.inclusions=**/*.go + +# Encoding of the source code. Default is default system encoding + +sonar.tests=. +sonar.test.inclusions=**/*_test.go +sonar.test.exclusions=**/*_generated*.go,**/*_generated/**,**/vendor/** + +sonar.sourceEncoding=UTF-8 \ No newline at end of file diff --git a/test/integration-with-configmanager.yml b/test/integration-with-configmanager.yml index 47daf92..85cd5df 100644 --- a/test/integration-with-configmanager.yml +++ b/test/integration-with-configmanager.yml @@ -1,6 +1,6 @@ # Sample application with login setup: - baseUrl: http://127.0.0.1:8090 + baseUrl: http://127.0.0.1:59886 # http://127.0.0.1:8090 continueOnError: false isSinglePageApp: true auth: @@ -9,7 +9,7 @@ auth: value: test@example.com selector: //*[@class="app-body"]/div[1]/main/div/form/div[2]/input password: - value: AWSPARAMSTR:///systemX/environment/appY-password + value: AWSPARAMSTR:///int-test/pocketbase/admin-pwd selector: //*[@class="app-body"]/div[1]/main/div/form/div[3]/input submit: selector: '#app > div > div > div.page-wrapper.full-page.center-content > main > div > form > button' diff --git a/test/parse.test.yml b/test/parse.test.yml new file mode 100644 index 0000000..e69de29 diff --git a/uistrategy.go b/uistrategy.go index efa2fb3..f783a5d 100644 --- a/uistrategy.go +++ b/uistrategy.go @@ -24,19 +24,22 @@ type Element struct { Timeout int } -type ActionReport struct { - Name string `json:"name"` - Screenshot string `json:"screenshot"` - Errored bool `json:"errored"` - Message string `json:"message"` +type ActionReportItem struct { + Screenshot string `json:"screenshot"` + Errored bool `json:"errored"` + Message string `json:"message"` + Output []string `json:"output"` } -type ViewReport struct { - Name string `json:"name"` - Message string `json:"message"` - Actions []ActionReport `json:"actions"` +type ActionsReport map[string]ActionReportItem + +type ViewReportItem struct { + Message string `json:"message"` + Actions ActionsReport `json:"actions"` } +type ViewReport map[string]ViewReportItem + type LoggedInPage struct { *Web page *rod.Page @@ -57,6 +60,7 @@ type WebConfig struct { UserMode bool `yaml:"userMode" json:"userMode"` DataDir string `yaml:"dataDir" json:"dataDir"` ReuseBrowserCache bool `yaml:"reuseBrowserCache" json:"reuseBrowserCache"` + NoSandbox bool `yaml:"noSandbox" json:"noSandbox"` } // BaseConfig is the base config object @@ -73,8 +77,8 @@ type UiStrategyConf struct { Setup BaseConfig `yaml:"setup" json:"setup"` // Auth is optional // should be omitted for apps that do not require a login - Auth *Auth `yaml:"auth,omitempty" json:"auth,omitempty"` - Actions []ViewAction `yaml:"actions" json:"actions"` + Auth *Auth `yaml:"auth,omitempty" json:"auth,omitempty"` + Actions []*ViewAction `yaml:"actions" json:"actions"` } // ViewAction defines a single action to do @@ -83,11 +87,11 @@ type UiStrategyConf struct { type ViewAction struct { navigate string `yaml:"-" json:"-"` // report attr - message string `yaml:"-" json:"-"` - Iframe *IframeAction `yaml:"iframe,omitempty" json:"iframe,omitempty"` - Name string `yaml:"name" json:"name"` - Navigate string `yaml:"navigate" json:"navigate"` - ElementActions []ElementAction `yaml:"elementActions" json:"elementActions"` + message string `yaml:"-" json:"-"` + Iframe *IframeAction `yaml:"iframe,omitempty" json:"iframe,omitempty"` + Name string `yaml:"name" json:"name"` + Navigate string `yaml:"navigate" json:"navigate"` + ElementActions []*ElementAction `yaml:"elementActions" json:"elementActions"` } // IframeAction @@ -106,10 +110,12 @@ type ElementAction struct { Element Element `yaml:"element" json:"element"` Assert bool `yaml:"assert,omitempty" json:"assert,omitempty"` SkipOnErrorMessage string `yaml:"skipOnErrorMessage,omitempty" json:"skipOnErrorMessage,omitempty"` + CaptureOutput bool `yaml:"captureOutput,omitempty" json:"captureOutput,omitempty"` // report attrs - message string - errored bool - screenshot string + message string + errored bool + screenshot string + capturedOutput []string // TODO: currently unused // Timeout int `yaml:"timeout" json:"timeout"` // InputText *string `yaml:"inputText,omitempty" json:"inputText,omitempty"` @@ -133,7 +139,7 @@ type Web struct { // with the provided BaseConfig func New(conf BaseConfig) *Web { _ = util.InitDirDeps() - url := newLauncher(conf.LauncherConfig).MustLaunch() + url := newLauncher(conf.LauncherConfig).NoSandbox(conf.LauncherConfig.NoSandbox).MustLaunch() browser := rod.New(). ControlURL(url). MustConnect().NoDefaultDevice() @@ -181,7 +187,7 @@ func (w *Web) WithLogger(l log.Logger) *Web { } // Drive runs a single UIStrategy in the same logged in session -func (web *Web) Drive(ctx context.Context, auth *Auth, allActions []ViewAction) []error { +func (web *Web) Drive(ctx context.Context, auth *Auth, allActions []*ViewAction) []error { var errs []error // and re-use same browser for all calls // defer web.browser.MustClose() @@ -195,8 +201,7 @@ func (web *Web) Drive(ctx context.Context, auth *Auth, allActions []ViewAction) // start driving in that session for _, v := range allActions { - vp := &v //.WithNavigate(web.config.BaseUrl) - if e := page.PerformActions(vp.WithNavigate(web.config.BaseUrl)); e != nil { + if e := page.PerformActions(v.WithNavigate(web.config.BaseUrl)); e != nil { errs = append(errs, e) } } @@ -225,11 +230,10 @@ func (lp *LoggedInPage) PerformActions(action *ViewAction) error { actionPage.MustWaitLoad() - for _, a := range action.ElementActions { + for _, ap := range action.ElementActions { // perform action - lp.log.Debugf("starting to perform action: %s", a.Name) + lp.log.Debugf("starting to perform action: %s", ap.Name) // end perform action - ap := &a if skip, e := lp.handleActionError(actionPage, ap, lp.performAction(actionPage, ap)); e != nil { if skip { break @@ -237,7 +241,7 @@ func (lp *LoggedInPage) PerformActions(action *ViewAction) error { return e } lp.errors = []error{} - lp.log.Debugf("completed action: %s", a.Name) + lp.log.Debugf("completed action: %s", ap.Name) } return nil } @@ -329,6 +333,19 @@ func (p *LoggedInPage) performAction(page *rod.Page, a *ElementAction) []error { rodElem, err := p.DetermineActionElement(page, a) a.errored = false a.screenshot = "" + if rodElem == nil { + a.errored = true + a.screenshot = p.captureAndSave(page) + p.errors = append(p.errors, fmt.Errorf("element not found")) + return p.errors + } + if a.CaptureOutput { + html, err := rodElem.HTML() + if err != nil { + p.errors = append(p.errors, err) + } + a.capturedOutput = append(a.capturedOutput, html) + } if err != nil { p.log.Debugf("action: %s, errored with %+#v", a.Name, err) // extend screenshots here @@ -403,16 +420,26 @@ func (lp *LoggedInPage) DetermineActionType(action *ElementAction, elem *rod.Ele // TODO: custom errors here return fmt.Errorf("assert set to true. unable to perform action: %+v. element not found", action) } + // update report with step miss lp.log.Debugf("element not found but ignoring error as assert is set to false") + return nil } - if elem != nil && action.Assert { - // update report with step found - // item found not performing action - lp.log.Debug("assert only returning early") - return nil + + if elem != nil { + if action.Assert { + // update report with step found + // item found not performing action + lp.log.Debug("only assert only returning early") + return nil + } + if action.CaptureOutput { + lp.log.Debug("capturing output only returning early") + return nil + } } + // if Value is present on the actionElement then always give preference if action.Element.Value != nil { lp.log.Debugf("action includes value on actionElement: %v", *action.Element.Value) diff --git a/uistrategy_samples_test.go b/uistrategy_samples_test.go index eaeb2b0..1e9e2db 100644 --- a/uistrategy_samples_test.go +++ b/uistrategy_samples_test.go @@ -1,4 +1,4 @@ -package uistrategy +package uistrategy_test var testHtml_style = []byte(`