├── generator ├── _testdata │ ├── e2e │ │ └── .gitkeep │ ├── benchmark │ │ └── .gitkeep │ ├── expectedE2E │ │ ├── nestedSuiteRes │ │ │ ├── js │ │ │ │ └── search_index.js │ │ │ ├── nested │ │ │ │ └── index.html │ │ │ └── index.html │ │ ├── simpleSuiteRes │ │ │ ├── js │ │ │ │ └── search_index.js │ │ │ └── index.html │ │ ├── minifiedSimpleSuiteRes │ │ │ ├── js │ │ │ │ └── search_index.js │ │ │ ├── index.html │ │ │ ├── skipped_specification.html │ │ │ └── failing_specification_1.html │ │ ├── simpleSuiteResWithHookScreenshots │ │ │ ├── js │ │ │ │ └── search_index.js │ │ │ └── index.html │ │ └── before_suite_fail.html │ └── integration │ │ ├── pass_index.html │ │ ├── spec_err.html │ │ ├── before_after_suite_fail.html │ │ ├── custom_screenshots.html │ │ └── skipped_spec.html ├── fragments.go ├── search.go ├── e2e_test.go └── transform_multiline_test.go ├── regenerate ├── _testdata │ ├── e2e │ │ └── .gitkeep │ ├── .gitignore │ └── expectedE2E │ │ └── simpleSuiteRes │ │ ├── js │ │ └── search_index.js │ │ └── index.html ├── regenerate.go └── regenerate_e2e_test.go ├── images └── sample.png ├── .gitignore ├── .gitmodules ├── themes └── default │ └── assets │ ├── images │ ├── close.gif │ ├── favicon.ico │ ├── loading.gif │ ├── overlay.png │ ├── Gauge-Logo.png │ └── gaugeLogo.png │ ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ └── open-sans │ │ ├── Bold │ │ ├── OpenSans-Bold.eot │ │ ├── OpenSans-Bold.ttf │ │ ├── OpenSans-Bold.woff │ │ └── OpenSans-Bold.woff2 │ │ ├── Light │ │ ├── OpenSans-Light.eot │ │ ├── OpenSans-Light.ttf │ │ ├── OpenSans-Light.woff │ │ └── OpenSans-Light.woff2 │ │ ├── Italic │ │ ├── OpenSans-Italic.eot │ │ ├── OpenSans-Italic.ttf │ │ ├── OpenSans-Italic.woff │ │ └── OpenSans-Italic.woff2 │ │ ├── Regular │ │ ├── OpenSans-Regular.eot │ │ ├── OpenSans-Regular.ttf │ │ ├── OpenSans-Regular.woff │ │ └── OpenSans-Regular.woff2 │ │ └── BoldItalic │ │ ├── OpenSans-BoldItalic.eot │ │ ├── OpenSans-BoldItalic.ttf │ │ ├── OpenSans-BoldItalic.woff │ │ └── OpenSans-BoldItalic.woff2 │ ├── css │ ├── open-sans.css │ └── normalize.css │ └── js │ ├── auto-complete.min.js │ └── clipboard.min.js ├── .github ├── dependabot.yml ├── workflows │ ├── golangci-lint.yml │ ├── test.yml │ └── deploy.yml └── issue_template.md ├── plugin.json ├── env ├── env_test.go └── env.go ├── go.mod ├── theme ├── theme.go └── theme_test.go ├── CONTRIBUTING.md ├── logger ├── log.go └── log_test.go ├── test_helper └── test_helper.go ├── handler.go ├── main.go ├── CODE_OF_CONDUCT.md ├── htmlReport_test.go ├── htmlReport.go ├── README.md └── go.sum /generator/_testdata/e2e/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /regenerate/_testdata/e2e/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /generator/_testdata/benchmark/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /regenerate/_testdata/.gitignore: -------------------------------------------------------------------------------- 1 | last_run_result -------------------------------------------------------------------------------- /images/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/images/sample.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | deploy 3 | .idea 4 | bin 5 | *.iml 6 | .DS_Store 7 | generator/_testdata/benchmark/*.html 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gauge-proto"] 2 | path = gauge-proto 3 | url = https://github.com/getgauge/gauge-proto.git 4 | -------------------------------------------------------------------------------- /themes/default/assets/images/close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/images/close.gif -------------------------------------------------------------------------------- /themes/default/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/images/favicon.ico -------------------------------------------------------------------------------- /themes/default/assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/images/loading.gif -------------------------------------------------------------------------------- /themes/default/assets/images/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/images/overlay.png -------------------------------------------------------------------------------- /themes/default/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /themes/default/assets/images/Gauge-Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/images/Gauge-Logo.png -------------------------------------------------------------------------------- /themes/default/assets/images/gaugeLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/images/gaugeLogo.png -------------------------------------------------------------------------------- /themes/default/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /themes/default/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /themes/default/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /themes/default/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Bold/OpenSans-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Bold/OpenSans-Bold.eot -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Bold/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Bold/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Bold/OpenSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Bold/OpenSans-Bold.woff -------------------------------------------------------------------------------- /regenerate/_testdata/expectedE2E/simpleSuiteRes/js/search_index.js: -------------------------------------------------------------------------------- 1 | var index = {"Tags":{"single word":["specs/example.html"]},"Specs":{"Specification Heading":["specs/example.html"]}}; -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Bold/OpenSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Bold/OpenSans-Bold.woff2 -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Light/OpenSans-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Light/OpenSans-Light.eot -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Light/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Light/OpenSans-Light.ttf -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Light/OpenSans-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Light/OpenSans-Light.woff -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Italic/OpenSans-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Italic/OpenSans-Italic.eot -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Italic/OpenSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Italic/OpenSans-Italic.ttf -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Italic/OpenSans-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Italic/OpenSans-Italic.woff -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Italic/OpenSans-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Italic/OpenSans-Italic.woff2 -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Light/OpenSans-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Light/OpenSans-Light.woff2 -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Regular/OpenSans-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Regular/OpenSans-Regular.eot -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Regular/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Regular/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Regular/OpenSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Regular/OpenSans-Regular.woff -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/Regular/OpenSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/Regular/OpenSans-Regular.woff2 -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/BoldItalic/OpenSans-BoldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/BoldItalic/OpenSans-BoldItalic.eot -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/BoldItalic/OpenSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/BoldItalic/OpenSans-BoldItalic.ttf -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/BoldItalic/OpenSans-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/BoldItalic/OpenSans-BoldItalic.woff -------------------------------------------------------------------------------- /themes/default/assets/fonts/open-sans/BoldItalic/OpenSans-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgauge/html-report/HEAD/themes/default/assets/fonts/open-sans/BoldItalic/OpenSans-BoldItalic.woff2 -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/nestedSuiteRes/js/search_index.js: -------------------------------------------------------------------------------- 1 | var index = {"Tags":{"bar":["passing_specification_1.html"],"foo":["passing_specification_1.html"],"tag1":["passing_specification_1.html"],"tag2":["passing_specification_1.html"]},"Specs":{"Nested Specification":["nested/nested_specification.html"],"Passing Specification 1":["passing_specification_1.html"]}}; -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/simpleSuiteRes/js/search_index.js: -------------------------------------------------------------------------------- 1 | var index = {"Tags":{"bar":["passing_specification_1.html"],"foo":["passing_specification_1.html"],"tag1":["passing_specification_1.html"],"tag2":["passing_specification_1.html"]},"Specs":{"Failing Specification 1":["failing_specification_1.html"],"Passing Specification 1":["passing_specification_1.html"],"Skipped Specification":["skipped_specification.html"]}}; -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/minifiedSimpleSuiteRes/js/search_index.js: -------------------------------------------------------------------------------- 1 | var index = {"Tags":{"bar":["passing_specification_1.html"],"foo":["passing_specification_1.html"],"tag1":["passing_specification_1.html"],"tag2":["passing_specification_1.html"]},"Specs":{"Failing Specification 1":["failing_specification_1.html"],"Passing Specification 1":["passing_specification_1.html"],"Skipped Specification":["skipped_specification.html"]}}; -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/simpleSuiteResWithHookScreenshots/js/search_index.js: -------------------------------------------------------------------------------- 1 | var index = {"Tags":{"bar":["passing_specification_1.html"],"foo":["passing_specification_1.html"],"tag1":["passing_specification_1.html"],"tag2":["passing_specification_1.html"]},"Specs":{"Failing Specification 1":["failing_specification_1.html"],"Passing Specification 1":["passing_specification_1.html"],"Skipped Specification":["skipped_specification.html"]}}; -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | allow: 8 | - dependency-type: all 9 | groups: 10 | go: 11 | patterns: 12 | - "*" 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: monthly 17 | groups: 18 | github-actions: 19 | patterns: 20 | - "*" -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | pull_request: 9 | jobs: 10 | golangci: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v6 15 | - name: Set up Go 16 | uses: actions/setup-go@v6 17 | with: 18 | check-latest: true 19 | go-version-file: 'go.mod' 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v9 22 | with: 23 | version: latest 24 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": [ 3 | "grpc_support" 4 | ], 5 | "command": { 6 | "darwin": [ 7 | "bin/html-report" 8 | ], 9 | "linux": [ 10 | "bin/html-report" 11 | ], 12 | "windows": [ 13 | "bin/html-report" 14 | ] 15 | }, 16 | "description": "Html reporting plugin", 17 | "gaugeVersionSupport": { 18 | "maximum": "", 19 | "minimum": "1.0.7" 20 | }, 21 | "id": "html-report", 22 | "install": { 23 | "darwin": [], 24 | "linux": [], 25 | "windows": [] 26 | }, 27 | "name": "Html Report", 28 | "scope": [ 29 | "Execution" 30 | ], 31 | "version": "4.4.0" 32 | } -------------------------------------------------------------------------------- /generator/fragments.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | 7 | package generator 8 | 9 | const ( 10 | textFragmentKind fragmentKind = iota 11 | staticFragmentKind 12 | dynamicFragmentKind 13 | specialStringFragmentKind 14 | specialTableFragmentKind 15 | tableFragmentKind 16 | multilineFragmentKind 17 | ) 18 | 19 | type fragmentKind int 20 | 21 | type fragment struct { 22 | FragmentKind fragmentKind 23 | Text string 24 | Name string 25 | Table *table 26 | FileName string 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | test: 11 | name: Go ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | 17 | steps: 18 | - uses: actions/checkout@v6 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v6 22 | with: 23 | check-latest: true 24 | go-version-file: 'go.mod' 25 | 26 | - name: Run tests 27 | run: | 28 | go test ./... 29 | 30 | - name: Build and Install 31 | run: | 32 | go run build/make.go 33 | go run build/make.go --install 34 | -------------------------------------------------------------------------------- /env/env_test.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import "testing" 4 | 5 | func TestMaxRecvMsgSize(t *testing.T) { 6 | t.Run("empty value should return default", func(t *testing.T) { 7 | t.Setenv(gaugeMaxMessageSize, "") 8 | v := GetMaxMessageSize() 9 | if v != 1024 { 10 | t.Errorf("Expected 1024, got %d", v) 11 | } 12 | }) 13 | 14 | t.Run("non-numeric should return default", func(t *testing.T) { 15 | t.Setenv(gaugeMaxMessageSize, "abcd") 16 | v := GetMaxMessageSize() 17 | if v != 1024 { 18 | t.Errorf("Expected 1024, got %d", v) 19 | } 20 | }) 21 | 22 | t.Run("numeric should return set value", func(t *testing.T) { 23 | t.Setenv(gaugeMaxMessageSize, "2048") 24 | v := GetMaxMessageSize() 25 | if v != 2048 { 26 | t.Errorf("Expected 2048, got %d", v) 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | ### Expected behavior 12 | Tell us what should happen 13 | 14 | ### Actual behavior 15 | Tell us what happens instead 16 | 17 | ### Steps to reproduce 18 | 1. 19 | 2. 20 | 3. 21 | 22 | ### Gauge version 23 | ``` 24 | Run gauge -v on your system and paste the results here. 25 | ``` 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/getgauge/html-report 2 | 3 | go 1.25 4 | 5 | require ( 6 | github.com/documize/html-diff v0.0.0-20160503140253-f61c192c7796 7 | github.com/getgauge/common v0.0.0-20251001154240-471505c641c5 8 | github.com/getgauge/gauge-proto/go/gauge_messages v0.0.0-20251009113823-9780b2b3681a 9 | github.com/kylelemons/godebug v1.1.0 10 | github.com/microcosm-cc/bluemonday v1.0.27 11 | github.com/russross/blackfriday v1.6.0 12 | github.com/tdewolff/minify/v2 v2.24.7 13 | google.golang.org/grpc v1.77.0 14 | google.golang.org/protobuf v1.36.10 15 | ) 16 | 17 | require ( 18 | github.com/aymerick/douceur v0.2.0 // indirect 19 | github.com/dmotylev/goproperties v0.0.0-20140630191356-7cbffbaada47 // indirect 20 | github.com/gorilla/css v1.0.1 // indirect 21 | github.com/mb0/diff v0.0.0-20131118162322-d8d9a906c24d // indirect 22 | github.com/tdewolff/parse/v2 v2.8.5 // indirect 23 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect 24 | golang.org/x/sys v0.38.0 // indirect 25 | golang.org/x/text v0.31.0 // indirect 26 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /theme/theme.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package theme 7 | 8 | import ( 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/getgauge/common" 13 | ) 14 | 15 | const ( 16 | reportThemeProperty = "GAUGE_HTML_REPORT_THEME_PATH" 17 | ) 18 | 19 | var templateBasePath string 20 | 21 | func GetDefaultThemePath(pluginsDir string) string { 22 | if templateBasePath == "" { 23 | templateBasePath = filepath.Join(pluginsDir, "themes") 24 | } 25 | return filepath.Join(templateBasePath, "default") 26 | } 27 | 28 | func CopyReportTemplateFiles(themePath, reportDir string) error { 29 | r := filepath.Join(themePath, "assets") 30 | _, err := common.MirrorDir(r, reportDir) 31 | return err 32 | } 33 | 34 | func GetThemePath(pluginsDir string) string { 35 | t := os.Getenv(reportThemeProperty) 36 | if t == "" { 37 | t = GetDefaultThemePath(pluginsDir) 38 | } 39 | return t 40 | } 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to Gauge 2 | 3 | Contributions to Gauge are welcome and appreciated. Please read this document to understand the process for contributing. 4 | 5 | ## Gauge Core v/s Plugins 6 | 7 | Gauge Core is a project that has features that would reflect across all Gauge use cases. These features are typically agnostic of the user's choice of implementation language. 8 | 9 | Plugins are meant to do something specific. These could be adding support for a new language, or have a new report etc. 10 | 11 | So, depending on where you see your contribution fit, please focus on the respective repository. 12 | 13 | ## Contribution process 14 | 15 | Please read about the Contribution Process [here](https://github.com/getgauge/gauge/blob/master/CONTRIBUTING.md), if you are happy please sign the [Contributor's License Agreement](https://gauge-bot.herokuapp.com/cla/). 16 | 17 | ## How can I contribute 18 | 19 | Contributions can be of many forms: 20 | 21 | - Open an issue, or participate in an existing one. 22 | - Write some code, and send us a pull request. 23 | - Enhance the documentation 24 | - Participate in design discussions on Google Groups 25 | 26 | If you need help in getting started with contribution, feel free to reach out on the [Google Groups](https://groups.google.com/forum/#!forum/getgauge) or [Gitter](https://gitter.im/getgauge/chat). 27 | -------------------------------------------------------------------------------- /regenerate/regenerate.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package regenerate 7 | 8 | import ( 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/getgauge/html-report/logger" 13 | 14 | "github.com/getgauge/gauge-proto/go/gauge_messages" 15 | "github.com/getgauge/html-report/env" 16 | "github.com/getgauge/html-report/generator" 17 | "github.com/getgauge/html-report/theme" 18 | "google.golang.org/protobuf/proto" 19 | ) 20 | 21 | // Report generates html report from saved result. 22 | func Report(inputFile, reportsDir, themePath, pRoot string) { 23 | b, err := os.ReadFile(inputFile) 24 | if err != nil { 25 | logger.Fatal(err.Error()) 26 | } 27 | psr := &gauge_messages.ProtoSuiteResult{} 28 | err = proto.Unmarshal(b, psr) 29 | if err != nil { 30 | logger.Fatalf("Unable to read last run data from %s. Error: %s", inputFile, err.Error()) 31 | } 32 | res := generator.ToSuiteResult(pRoot, psr) 33 | 34 | env.CreateDirectory(reportsDir) 35 | if themePath == "" { 36 | workingDir, _ := env.GetCurrentExecutableDir() 37 | themePath = theme.GetDefaultThemePath(filepath.Dir(workingDir)) 38 | } 39 | generator.GenerateReport(res, reportsDir, themePath, true) 40 | } 41 | -------------------------------------------------------------------------------- /logger/log.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type logMessage struct { 10 | Level string `json:"logLevel"` 11 | Message string `json:"message"` 12 | } 13 | 14 | func (message *logMessage) toJSON() (string, error) { 15 | jsonMessage, err := json.Marshal(message) 16 | if err != nil { 17 | return "", err 18 | } 19 | return string(jsonMessage), nil 20 | } 21 | 22 | // Init initialize logger 23 | func Init() { 24 | } 25 | 26 | // Debug logs debug message 27 | func Debug(msg string) { 28 | log("debug", msg) 29 | } 30 | 31 | // Debugf logs debug message 32 | func Debugf(format string, args ...interface{}) { 33 | Debug(fmt.Sprintf(format, args...)) 34 | } 35 | 36 | // Info logs debug message 37 | func Info(msg string) { 38 | log("info", msg) 39 | } 40 | 41 | // Infof logs info message 42 | func Infof(format string, args ...interface{}) { 43 | Info(fmt.Sprintf(format, args...)) 44 | } 45 | 46 | // Fatal logs CRITICAL messages and exits 47 | func Fatal(msg string) { 48 | log("fatal", msg) 49 | os.Exit(1) 50 | } 51 | 52 | // Fatalf logs CRITICAL messages and exits 53 | func Fatalf(format string, args ...interface{}) { 54 | Fatal(fmt.Sprintf(format, args...)) 55 | } 56 | 57 | // Warnf logs warning message 58 | func Warnf(format string, args ...interface{}) { 59 | log("warning", fmt.Sprintf(format, args...)) 60 | } 61 | 62 | func log(logLevel string, msg string) { 63 | message := logMessage{Level: logLevel, Message: msg} 64 | m, _ := message.toJSON() 65 | if _, err := fmt.Fprintln(os.Stdout, m); err != nil { 66 | return 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /theme/theme_test.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package theme 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "testing" 14 | "time" 15 | 16 | helper "github.com/getgauge/html-report/test_helper" 17 | ) 18 | 19 | func init() { 20 | templateBasePath = filepath.Join("..", "themes") 21 | } 22 | 23 | func TestCopyingReportTemplates(t *testing.T) { 24 | dirToCopy := filepath.Join(os.TempDir(), randomName()) 25 | defer func(path string) { 26 | if err := os.RemoveAll(path); err != nil { 27 | t.Errorf("Failed to remove directory %s: %v", path, err) 28 | } 29 | }(dirToCopy) 30 | 31 | err := CopyReportTemplateFiles(GetThemePath(""), dirToCopy) 32 | if err != nil { 33 | t.Errorf("Expected error == nil, got: %s \n", err.Error()) 34 | } 35 | verifyReportTemplateFilesAreCopied(dirToCopy, t) 36 | } 37 | 38 | func verifyReportTemplateFilesAreCopied(dest string, t *testing.T) { 39 | reportDir := filepath.Join(GetThemePath(""), "assets") 40 | err := filepath.Walk(reportDir, func(path string, info os.FileInfo, err error) error { 41 | path = strings.Replace(path, reportDir, "", 1) 42 | destFilePath := filepath.Join(dest, path) 43 | if !helper.FileExists(destFilePath) { 44 | t.Errorf("File %s not copied.", destFilePath) 45 | } 46 | return nil 47 | }) 48 | if err != nil { 49 | t.Errorf("unable to walk %s. %s", reportDir, err.Error()) 50 | } 51 | } 52 | 53 | func randomName() string { 54 | return fmt.Sprintf("%d", time.Now().UnixNano()) 55 | } 56 | -------------------------------------------------------------------------------- /logger/log_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | var old = os.Stdout 10 | var fname = filepath.Join(os.TempDir(), "stdout") 11 | 12 | func runLogTest(t *testing.T, logFunc func(), expected string) { 13 | temp := setStdout() 14 | defer func() { 15 | if err := temp.Close(); err != nil { 16 | t.Errorf("failed to close temp file: %v", err) 17 | } 18 | os.Stdout = old 19 | }() 20 | 21 | logFunc() 22 | got, _ := os.ReadFile(fname) 23 | if expected != string(got) { 24 | t.Errorf("Expected %s to be => %s", expected, got) 25 | } 26 | } 27 | 28 | func TestDebugShouldWriteLogInJsonFormat(t *testing.T) { 29 | runLogTest(t, func() { Debug("log debug message") }, "{\"logLevel\":\"debug\",\"message\":\"log debug message\"}\n") 30 | } 31 | 32 | func TestDebugfShouldWriteLogInJsonFormat(t *testing.T) { 33 | runLogTest(t, func() { Debugf("log %s debug message", "formatted") }, "{\"logLevel\":\"debug\",\"message\":\"log formatted debug message\"}\n") 34 | } 35 | 36 | func TestInfoShouldWriteLogInJsonFormat(t *testing.T) { 37 | runLogTest(t, func() { Info("log info message") }, "{\"logLevel\":\"info\",\"message\":\"log info message\"}\n") 38 | } 39 | 40 | func TestInfofShouldWriteLogInJsonFormat(t *testing.T) { 41 | runLogTest(t, func() { Infof("log %s info message", "formatted") }, "{\"logLevel\":\"info\",\"message\":\"log formatted info message\"}\n") 42 | } 43 | 44 | func TestWarnfShouldWriteLogInJsonFormat(t *testing.T) { 45 | runLogTest(t, func() { Warnf("log %s warning message", "formatted") }, "{\"logLevel\":\"warning\",\"message\":\"log formatted warning message\"}\n") 46 | } 47 | 48 | func setStdout() *os.File { 49 | temp, _ := os.Create(fname) 50 | os.Stdout = temp 51 | return temp 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Release on PR Merge 2 | 3 | on: deployment 4 | 5 | jobs: 6 | release-and-bump-version: 7 | if: github.event.deployment.environment == 'production' 8 | runs-on: ubuntu-latest 9 | env: 10 | GITHUB_TOKEN: '${{ secrets.GAUGEBOT_GITHUB_TOKEN }}' 11 | 12 | steps: 13 | - uses: actions/checkout@v6 14 | - name: Set up Go 15 | uses: actions/setup-go@v6 16 | with: 17 | check-latest: true 18 | go-version-file: 'go.mod' 19 | 20 | - name: build 21 | run: | 22 | go run build/make.go --all-platforms 23 | go run build/make.go --all-platforms --distro 24 | 25 | - name: Setup git 26 | run: | 27 | git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" 28 | git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)" 29 | 30 | - name: Deploy 31 | run: | 32 | if [ -z "$version" ]; then 33 | version=$(cd deploy && ls html-report* | head -1 | sed "s/\.[^\.]*$//" | sed "s/html-report-//" | sed "s/-[a-z]*\.[a-z0-9_]*$//"); 34 | fi 35 | echo "VERSION=$version" >> $GITHUB_ENV 36 | 37 | echo "---------------------------" 38 | echo "Updating release v$version" 39 | echo "---------------------------" 40 | echo -e "Gauge HTML Report v$version\n\n" > desc.txt 41 | release_description=$(ruby -e "$(curl -sSfL https://github.com/getgauge/gauge/raw/master/build/create_release_text.rb)" getgauge html-report) 42 | echo "$release_description" >> desc.txt 43 | gh release create --title "Gauge Html Report v${version}" --notes-file ./desc.txt "v${version}" deploy/* 44 | 45 | - name: Update metadata in gauge-repository 46 | run: | 47 | git clone https://github.com/getgauge/gauge-repository.git 48 | cd gauge-repository 49 | python update_metadata.py html-report $VERSION 50 | commit_message=$(echo -e "Update html-report to v$VERSION") 51 | git commit -am "$commit_message" 52 | git push "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/getgauge/gauge-repository.git" master 53 | -------------------------------------------------------------------------------- /test_helper/test_helper.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package testHelper 7 | 8 | import ( 9 | "fmt" 10 | "html" 11 | "os" 12 | "regexp" 13 | "testing" 14 | 15 | htmldiff "github.com/documize/html-diff" 16 | ) 17 | 18 | var re = regexp.MustCompile("[\\s]*[\n\t][\\s]*") 19 | 20 | func RemoveNewline(s string) string { 21 | return re.ReplaceAllLiteralString(s, "") 22 | } 23 | 24 | func AssertEqual(expected, actual, testName string, t *testing.T) { 25 | if expected != actual { 26 | diffHTML := compare(expected, actual) 27 | tmpFile, err := os.CreateTemp("", "") 28 | if err != nil { 29 | t.Errorf("Unable to dump to tmp file. Raw content:\n%s\n", diffHTML) 30 | } 31 | fileName := fmt.Sprintf("%s.html", tmpFile.Name()) 32 | err = os.WriteFile(fileName, []byte(diffHTML), 0644) 33 | if err != nil { 34 | t.Errorf("Unable to write file %s. Error: %s", fileName, err.Error()) 35 | } 36 | 37 | _ = tmpFile.Close() 38 | t.Errorf("%s - View Diff Output : %s\n", testName, fileName) 39 | } 40 | } 41 | 42 | func compare(a, b string) string { 43 | var cfg = &htmldiff.Config{ 44 | InsertedSpan: []htmldiff.Attribute{{Key: "style", Val: "background-color: palegreen;"}}, 45 | DeletedSpan: []htmldiff.Attribute{{Key: "style", Val: "background-color: lightpink;"}}, 46 | ReplacedSpan: []htmldiff.Attribute{{Key: "style", Val: "background-color: lightskyblue;"}}, 47 | CleanTags: []string{""}, 48 | } 49 | 50 | res, _ := cfg.HTMLdiff([]string{html.EscapeString(a), html.EscapeString(b)}) 51 | return res[0] 52 | } 53 | 54 | func FileExists(path string) bool { 55 | _, err := os.Stat(path) 56 | if err == nil { 57 | return true 58 | } 59 | return !os.IsNotExist(err) 60 | } 61 | 62 | func SetEnvOrFail(t *testing.T, key, value string) { 63 | if err := os.Setenv(key, value); err != nil { 64 | t.Logf("Failed to set env %s: %v", key, err) 65 | } 66 | } 67 | 68 | func UnsetEnvOrFail(t *testing.T, key string) { 69 | if err := os.Unsetenv(key); err != nil { 70 | t.Logf("Failed to unset env %s: %v", key, err) 71 | } 72 | } 73 | 74 | func RemoveOrFail(t *testing.T, key string) { 75 | if err := os.Remove(key); err != nil { 76 | t.Logf("Failed to set env %s: %v", key, err) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /regenerate/regenerate_e2e_test.go: -------------------------------------------------------------------------------- 1 | package regenerate 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/getgauge/gauge-proto/go/gauge_messages" 11 | helper "github.com/getgauge/html-report/test_helper" 12 | "google.golang.org/protobuf/proto" 13 | ) 14 | 15 | var templateBasePath, _ = filepath.Abs(filepath.Join("..", "themes", "default")) 16 | 17 | // setup converts _testdata/last_run_result.json to _testdata/last_run_result (binary serialized) 18 | // editing json is easy, helps in maintaining the setup data 19 | // but the actual regenerate requires serialized proto data, hence this conversion. 20 | func setup() { 21 | inputFile := filepath.Join("_testdata", "last_run_result.json") 22 | b, err := os.ReadFile(inputFile) 23 | if err != nil { 24 | log.Fatal(err.Error()) 25 | } 26 | psr := &gauge_messages.ProtoSuiteResult{} 27 | err = json.Unmarshal(b, psr) 28 | if err != nil { 29 | log.Fatalf("Unable to read last run data from %s. Error: %s", inputFile, err.Error()) 30 | } 31 | by, _ := proto.Marshal(psr) 32 | f := filepath.Join("_testdata", "last_run_result") 33 | err = os.WriteFile(f, by, 0644) 34 | if err != nil { 35 | log.Fatalf("Unable to write file %s. Error: %s", f, err.Error()) 36 | } 37 | } 38 | 39 | func TestEndToEndHTMLGenerationFromSavedResult(t *testing.T) { 40 | setup() 41 | expectedFiles := []string{"index.html", "specs/example.html", "js/search_index.js"} 42 | reportDir := filepath.Join("_testdata", "e2e") 43 | inputFile := filepath.Join("_testdata", "last_run_result") 44 | 45 | Report(inputFile, reportDir, templateBasePath, "/tmp/foo/") 46 | for _, expectedFile := range expectedFiles { 47 | gotContent, err := os.ReadFile(filepath.Join(reportDir, expectedFile)) 48 | if err != nil { 49 | t.Errorf("Error reading generated HTML file: %s", err.Error()) 50 | } 51 | wantContent, err := os.ReadFile(filepath.Join("_testdata", "expectedE2E", "simpleSuiteRes", expectedFile)) 52 | if err != nil { 53 | t.Errorf("Error reading expected HTML file: %s", err.Error()) 54 | } 55 | got := helper.RemoveNewline(string(gotContent)) 56 | want := helper.RemoveNewline(string(wantContent)) 57 | helper.AssertEqual(want, got, expectedFile, t) 58 | } 59 | cleanUp(t, reportDir) 60 | } 61 | 62 | func cleanUp(t *testing.T, reportDir string) { 63 | s, err := filepath.Glob(filepath.Join(reportDir, "*")) 64 | if err != nil { 65 | t.Error(err) 66 | } 67 | for _, f := range s { 68 | if f != filepath.Join(reportDir, ".gitkeep") { 69 | if err := os.RemoveAll(f); err != nil { 70 | return // error removing file 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | 7 | package main 8 | 9 | import ( 10 | "context" 11 | "os" 12 | 13 | "github.com/getgauge/gauge-proto/go/gauge_messages" 14 | "google.golang.org/grpc" 15 | ) 16 | 17 | type handler struct { 18 | gauge_messages.UnimplementedReporterServer 19 | server *grpc.Server 20 | } 21 | 22 | // NotifyConceptExecutionEnding implements gauge_messages.ReporterServer. 23 | func (*handler) NotifyConceptExecutionEnding(context.Context, *gauge_messages.ConceptExecutionEndingRequest) (*gauge_messages.Empty, error) { 24 | return &gauge_messages.Empty{}, nil 25 | } 26 | 27 | // NotifyConceptExecutionStarting implements gauge_messages.ReporterServer. 28 | func (*handler) NotifyConceptExecutionStarting(context.Context, *gauge_messages.ConceptExecutionStartingRequest) (*gauge_messages.Empty, error) { 29 | return &gauge_messages.Empty{}, nil 30 | } 31 | 32 | func (h *handler) NotifyExecutionStarting(c context.Context, m *gauge_messages.ExecutionStartingRequest) (*gauge_messages.Empty, error) { 33 | return &gauge_messages.Empty{}, nil 34 | } 35 | func (h *handler) NotifySpecExecutionStarting(c context.Context, m *gauge_messages.SpecExecutionStartingRequest) (*gauge_messages.Empty, error) { 36 | return &gauge_messages.Empty{}, nil 37 | } 38 | func (h *handler) NotifyScenarioExecutionStarting(c context.Context, m *gauge_messages.ScenarioExecutionStartingRequest) (*gauge_messages.Empty, error) { 39 | return &gauge_messages.Empty{}, nil 40 | } 41 | func (h *handler) NotifyStepExecutionStarting(c context.Context, m *gauge_messages.StepExecutionStartingRequest) (*gauge_messages.Empty, error) { 42 | return &gauge_messages.Empty{}, nil 43 | } 44 | func (h *handler) NotifyStepExecutionEnding(c context.Context, m *gauge_messages.StepExecutionEndingRequest) (*gauge_messages.Empty, error) { 45 | return &gauge_messages.Empty{}, nil 46 | } 47 | func (h *handler) NotifyScenarioExecutionEnding(c context.Context, m *gauge_messages.ScenarioExecutionEndingRequest) (*gauge_messages.Empty, error) { 48 | return &gauge_messages.Empty{}, nil 49 | } 50 | func (h *handler) NotifySpecExecutionEnding(c context.Context, m *gauge_messages.SpecExecutionEndingRequest) (*gauge_messages.Empty, error) { 51 | return &gauge_messages.Empty{}, nil 52 | } 53 | func (h *handler) NotifyExecutionEnding(c context.Context, m *gauge_messages.ExecutionEndingRequest) (*gauge_messages.Empty, error) { 54 | return &gauge_messages.Empty{}, nil 55 | } 56 | 57 | func (h *handler) NotifySuiteResult(c context.Context, m *gauge_messages.SuiteExecutionResult) (*gauge_messages.Empty, error) { 58 | createReport(m, true) 59 | return &gauge_messages.Empty{}, nil 60 | } 61 | 62 | func (h *handler) Kill(c context.Context, m *gauge_messages.KillProcessRequest) (*gauge_messages.Empty, error) { 63 | defer h.stopServer() 64 | return &gauge_messages.Empty{}, nil 65 | } 66 | 67 | func (h *handler) stopServer() { 68 | h.server.Stop() 69 | os.Exit(0) 70 | } 71 | -------------------------------------------------------------------------------- /themes/default/assets/css/open-sans.css: -------------------------------------------------------------------------------- 1 | /* BEGIN Light */ 2 | @font-face { 3 | font-family: 'Open Sans'; 4 | src: url("../fonts/open-sans/Light/OpenSans-Light.eot?v=1.1.0"); 5 | src: url("../fonts/open-sans/Light/OpenSans-Light.eot?#iefix&v=1.1.0") format("embedded-opentype"), url("../fonts/open-sans/Light/OpenSans-Light.woff2?v=1.1.0") format("woff2"), url("../fonts/open-sans/Light/OpenSans-Light.woff?v=1.1.0") format("woff"), url("../fonts/open-sans/Light/OpenSans-Light.ttf?v=1.1.0") format("truetype"), url("../fonts/open-sans/Light/OpenSans-Light.svg?v=1.1.0#Light") format("svg"); 6 | font-weight: 300; 7 | font-style: normal; } 8 | /* END Light */ 9 | /* BEGIN Regular */ 10 | @font-face { 11 | font-family: 'Open Sans'; 12 | src: url("../fonts/open-sans/Regular/OpenSans-Regular.eot?v=1.1.0"); 13 | src: url("../fonts/open-sans/Regular/OpenSans-Regular.eot?#iefix&v=1.1.0") format("embedded-opentype"), url("../fonts/open-sans/Regular/OpenSans-Regular.woff2?v=1.1.0") format("woff2"), url("../fonts/open-sans/Regular/OpenSans-Regular.woff?v=1.1.0") format("woff"), url("../fonts/open-sans/Regular/OpenSans-Regular.ttf?v=1.1.0") format("truetype"), url("../fonts/open-sans/Regular/OpenSans-Regular.svg?v=1.1.0#Regular") format("svg"); 14 | font-weight: normal; 15 | font-style: normal; } 16 | /* END Regular */ 17 | /* BEGIN Italic */ 18 | @font-face { 19 | font-family: 'Open Sans'; 20 | src: url("../fonts/open-sans/Italic/OpenSans-Italic.eot?v=1.1.0"); 21 | src: url("../fonts/open-sans/Italic/OpenSans-Italic.eot?#iefix&v=1.1.0") format("embedded-opentype"), url("../fonts/open-sans/Italic/OpenSans-Italic.woff2?v=1.1.0") format("woff2"), url("../fonts/open-sans/Italic/OpenSans-Italic.woff?v=1.1.0") format("woff"), url("../fonts/open-sans/Italic/OpenSans-Italic.ttf?v=1.1.0") format("truetype"), url("../fonts/open-sans/Italic/OpenSans-Italic.svg?v=1.1.0#Italic") format("svg"); 22 | font-weight: normal; 23 | font-style: italic; } 24 | /* END Italic */ 25 | /* BEGIN Bold */ 26 | @font-face { 27 | font-family: 'Open Sans'; 28 | src: url("../fonts/open-sans/Bold/OpenSans-Bold.eot?v=1.1.0"); 29 | src: url("../fonts/open-sans/Bold/OpenSans-Bold.eot?#iefix&v=1.1.0") format("embedded-opentype"), url("../fonts/open-sans/Bold/OpenSans-Bold.woff2?v=1.1.0") format("woff2"), url("../fonts/open-sans/Bold/OpenSans-Bold.woff?v=1.1.0") format("woff"), url("../fonts/open-sans/Bold/OpenSans-Bold.ttf?v=1.1.0") format("truetype"), url("../fonts/open-sans/Bold/OpenSans-Bold.svg?v=1.1.0#Bold") format("svg"); 30 | font-weight: bold; 31 | font-style: normal; } 32 | /* END Bold */ 33 | /* BEGIN Bold Italic */ 34 | @font-face { 35 | font-family: 'Open Sans'; 36 | src: url("../fonts/open-sans/BoldItalic/OpenSans-BoldItalic.eot?v=1.1.0"); 37 | src: url("../fonts/open-sans/BoldItalic/OpenSans-BoldItalic.eot?#iefix&v=1.1.0") format("embedded-opentype"), url("../fonts/open-sans/BoldItalic/OpenSans-BoldItalic.woff2?v=1.1.0") format("woff2"), url("../fonts/open-sans/BoldItalic/OpenSans-BoldItalic.woff?v=1.1.0") format("woff"), url("../fonts/open-sans/BoldItalic/OpenSans-BoldItalic.ttf?v=1.1.0") format("truetype"), url("../fonts/open-sans/BoldItalic/OpenSans-BoldItalic.svg?v=1.1.0#BoldItalic") format("svg"); 38 | font-weight: bold; 39 | font-style: italic; } 40 | /* END Bold Italic */ 41 | -------------------------------------------------------------------------------- /generator/search.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package generator 7 | 8 | import ( 9 | "encoding/json" 10 | "fmt" 11 | "os" 12 | "path/filepath" 13 | 14 | "github.com/getgauge/gauge-proto/go/gauge_messages" 15 | 16 | "github.com/getgauge/html-report/env" 17 | ) 18 | 19 | func NewSearchIndex() *SearchIndex { 20 | var i SearchIndex 21 | i.Tags = make(map[string][]string) 22 | i.Specs = make(map[string][]string) 23 | return &i 24 | } 25 | 26 | func (i *SearchIndex) hasValueForTag(tag string, spec string) bool { 27 | for _, s := range i.Tags[tag] { 28 | if s == spec { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | 35 | func (i *SearchIndex) hasSpec(specHeading string, specFileName string) bool { 36 | for _, s := range i.Specs[specHeading] { 37 | if s == specFileName { 38 | return true 39 | } 40 | } 41 | return false 42 | } 43 | 44 | func (i *SearchIndex) AddRawSpec(r *gauge_messages.ProtoSpec) { 45 | specFileName := toHTMLFileName(r.FileName, projectRoot) 46 | for _, t := range r.Tags { 47 | if !i.hasValueForTag(t, specFileName) { 48 | i.Tags[t] = append(i.Tags[t], specFileName) 49 | } 50 | } 51 | specHeading := r.SpecHeading 52 | if !i.hasSpec(specHeading, specFileName) { 53 | i.Specs[specHeading] = append(i.Specs[specHeading], specFileName) 54 | } 55 | } 56 | 57 | func (i *SearchIndex) AddRawItem(r *gauge_messages.ProtoItem) { 58 | specFileName := toHTMLFileName(r.FileName, projectRoot) 59 | if r.ItemType == gauge_messages.ProtoItem_Scenario { 60 | for _, t := range r.Scenario.Tags { 61 | if !i.hasValueForTag(t, specFileName) { 62 | i.Tags[t] = append(i.Tags[t], specFileName) 63 | } 64 | } 65 | } 66 | } 67 | 68 | func (i *SearchIndex) add(r *spec) { 69 | specFileName := toHTMLFileName(r.FileName, projectRoot) 70 | for _, t := range r.Tags { 71 | if !i.hasValueForTag(t, specFileName) { 72 | i.Tags[t] = append(i.Tags[t], specFileName) 73 | } 74 | } 75 | for _, s := range r.Scenarios { 76 | for _, t := range s.Tags { 77 | if !i.hasValueForTag(t, specFileName) { 78 | i.Tags[t] = append(i.Tags[t], specFileName) 79 | } 80 | } 81 | } 82 | specHeading := r.SpecHeading 83 | if !i.hasSpec(specHeading, specFileName) { 84 | i.Specs[specHeading] = append(i.Specs[specHeading], specFileName) 85 | } 86 | } 87 | 88 | func (i *SearchIndex) Write(dir string) error { 89 | env.CreateDirectory(filepath.Join(dir, "js")) 90 | f, err := os.Create(filepath.Join(dir, "js", "search_index.js")) 91 | if err != nil { 92 | return err 93 | } 94 | defer func() { 95 | if err := f.Close(); err != nil { 96 | return 97 | } 98 | }() 99 | s, err := json.Marshal(i) 100 | if err != nil { 101 | return err 102 | } 103 | _, err = fmt.Fprintf(f, "var index = %s;", s) 104 | return err 105 | } 106 | 107 | func generateSearchIndex(suiteRes *SuiteResult, reportsDir string) error { 108 | index := NewSearchIndex() 109 | for _, r := range suiteRes.SpecResults { 110 | index.add(r) 111 | } 112 | return index.Write(reportsDir) 113 | } 114 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package main 7 | 8 | import ( 9 | "flag" 10 | "fmt" 11 | "net" 12 | "os" 13 | 14 | "github.com/getgauge/common" 15 | "github.com/getgauge/gauge-proto/go/gauge_messages" 16 | "github.com/getgauge/html-report/env" 17 | "github.com/getgauge/html-report/logger" 18 | "github.com/getgauge/html-report/regenerate" 19 | "google.golang.org/grpc" 20 | ) 21 | 22 | const usage = `Usage of using_flag: 23 | -i, --input Source file to generate report from. This should be generated in /.gauge folder. 24 | -o, --output Output location for generating report. Will create directory if it doesn't exist. 25 | -t, --theme Theme to use for generating html report. 'default' theme will be used if not specified. 26 | -h, --help prints help information 27 | ` 28 | 29 | func main() { 30 | var inputFile string 31 | flag.StringVar(&inputFile, "input", "", "Source file to generate report from. This should be generated in /.gauge folder.") 32 | flag.StringVar(&inputFile, "i", "", "Source file to generate report from. This should be generated in /.gauge folder.") 33 | var outDir string 34 | flag.StringVar(&outDir, "output", "", "Output location for generating report. Will create directory if it doesn't exist.") 35 | flag.StringVar(&outDir, "o", "", "Output location for generating report. Will create directory if it doesn't exist.") 36 | var themePath string 37 | flag.StringVar(&themePath, "theme", "", "Theme to use for generating html report. 'default' theme will be used if not specified.") 38 | flag.StringVar(&themePath, "t", "", "Theme to use for generating html report. 'default' theme will be used if not specified.") 39 | 40 | flag.Usage = func() { fmt.Print(usage) } 41 | flag.Parse() 42 | if inputFile != "" { 43 | if outDir == "" { 44 | flag.PrintDefaults() 45 | os.Exit(1) 46 | } 47 | projectRoot, err := common.GetProjectRoot() 48 | if err != nil { 49 | logger.Fatalf("%s", err.Error()) 50 | } 51 | if !common.FileExists(inputFile) { 52 | logger.Fatalf("Input file does not exist: %s", inputFile) 53 | } 54 | regenerate.Report(inputFile, outDir, themePath, projectRoot) 55 | return 56 | } 57 | 58 | switch action := os.Getenv(pluginActionEnv); action { 59 | case setupAction: 60 | env.AddDefaultPropertiesToProject() 61 | case executionAction: 62 | pluginsDir, _ = os.Getwd() 63 | err := os.Chdir(env.GetProjectRoot()) 64 | if err != nil { 65 | logger.Fatalf("failed to chdir to %s. %s", pluginsDir, err.Error()) 66 | } 67 | 68 | address, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") 69 | if err != nil { 70 | logger.Fatalf("failed to start server.") 71 | } 72 | l, err := net.ListenTCP("tcp", address) 73 | if err != nil { 74 | logger.Fatalf("failed to start server.") 75 | } 76 | mSize := env.GetMaxMessageSize() 77 | logger.Debugf("Setting MaxRecvMsgSize = %d MB", mSize) 78 | server := grpc.NewServer(grpc.MaxRecvMsgSize(mSize * 1024 * 1024)) 79 | h := &handler{server: server} 80 | gauge_messages.RegisterReporterServer(server, h) 81 | logger.Infof("Listening on port:%d", l.Addr().(*net.TCPAddr).Port) 82 | err = server.Serve(l) 83 | if err != nil { 84 | logger.Fatalf("failed to start server. %s", err.Error()) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at gauge-coc@thoughtworks.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /env/env.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package env 7 | 8 | import ( 9 | "os" 10 | "path" 11 | "path/filepath" 12 | "strconv" 13 | "strings" 14 | 15 | "github.com/getgauge/html-report/logger" 16 | 17 | "github.com/getgauge/common" 18 | ) 19 | 20 | const ( 21 | DefaultReportsDir = "reports" 22 | GaugeReportsDirEnvName = "gauge_reports_dir" // directory where reports are generated by plugins 23 | ScreenshotsDirName = "gauge_screenshots_dir" // directory where screenshots are stored 24 | OverwriteReportsEnvProperty = "overwrite_reports" 25 | UseNestedSpecs = "use_nested_specs" 26 | SaveExecutionResult = "save_execution_result" 27 | pluginKillTimeout = "plugin_kill_timeout" 28 | gaugeMinifyReports = "gauge_minify_reports" 29 | gaugeMaxMessageSize = "gauge_max_message_size" 30 | ) 31 | 32 | func GetCurrentExecutableDir() (string, string) { 33 | ex, err := os.Executable() 34 | if err != nil { 35 | logger.Fatal(err.Error()) 36 | } 37 | target, err := filepath.EvalSymlinks(ex) 38 | if err != nil { 39 | return path.Dir(ex), filepath.Base(ex) 40 | } 41 | return filepath.Dir(target), filepath.Base(ex) 42 | } 43 | 44 | // CreateDirectory creates given directory if it doesn't exist 45 | func CreateDirectory(dir string) { 46 | if err := os.MkdirAll(dir, common.NewDirectoryPermissions); err != nil { 47 | logger.Fatalf("Failed to create directory %s: %s\n", dir, err) 48 | } 49 | } 50 | 51 | var GetProjectRoot = func() string { 52 | projectRoot := os.Getenv(common.GaugeProjectRootEnv) 53 | if projectRoot == "" { 54 | logger.Fatalf("Environment variable '%s' is not set. \n", common.GaugeProjectRootEnv) 55 | } 56 | return projectRoot 57 | } 58 | 59 | func AddDefaultPropertiesToProject() { 60 | defaultPropertiesFile := getDefaultPropertiesFile() 61 | 62 | reportsDirProperty := &(common.Property{ 63 | Comment: "The path to the gauge reports directory. Should be either relative to the project directory or an absolute path", 64 | Name: GaugeReportsDirEnvName, 65 | DefaultValue: DefaultReportsDir}) 66 | 67 | overwriteReportProperty := &(common.Property{ 68 | Comment: "Set as false if gauge reports should not be overwritten on each execution. A new time-stamped directory will be created on each execution.", 69 | Name: OverwriteReportsEnvProperty, 70 | DefaultValue: "true"}) 71 | 72 | if !common.FileExists(defaultPropertiesFile) { 73 | logger.Debugf("Failed to setup html report plugin in project. Default properties file does not exist at %s. \n", defaultPropertiesFile) 74 | return 75 | } 76 | if err := common.AppendProperties(defaultPropertiesFile, reportsDirProperty, overwriteReportProperty); err != nil { 77 | logger.Debugf("Failed to setup html report plugin in project: %s \n", err) 78 | return 79 | } 80 | logger.Debug("Succesfully added configurations for html-report to env/default/default.properties") 81 | } 82 | 83 | func getDefaultPropertiesFile() string { 84 | return filepath.Join(GetProjectRoot(), "env", "default", "default.properties") 85 | } 86 | 87 | func ShouldOverwriteReports() bool { 88 | return isEnvSet(OverwriteReportsEnvProperty) 89 | } 90 | 91 | func ShouldUseNestedSpecs() bool { 92 | return isEnvSet(UseNestedSpecs) 93 | } 94 | 95 | func ShouldMinifyReports() bool { 96 | return isEnvSet(gaugeMinifyReports) 97 | } 98 | 99 | func isEnvSet(envName string) bool { 100 | envValue := os.Getenv(envName) 101 | return strings.ToLower(envValue) == "true" 102 | } 103 | 104 | func GetMaxMessageSize() int { 105 | m := os.Getenv(gaugeMaxMessageSize) 106 | r, err := strconv.Atoi(m) 107 | if err != nil { 108 | return 1024 109 | } 110 | return r 111 | } 112 | 113 | // PluginKillTimeout returns the plugin_kill_timeout in seconds 114 | var PluginKillTimeout = func() int { 115 | e := os.Getenv(pluginKillTimeout) 116 | if e == "" { 117 | return 0 118 | } 119 | v, err := strconv.Atoi(e) 120 | if err != nil { 121 | return 0 122 | } 123 | return v / 1000 124 | } 125 | -------------------------------------------------------------------------------- /htmlReport_test.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "path/filepath" 12 | "reflect" 13 | "runtime" 14 | "testing" 15 | "time" 16 | 17 | "github.com/getgauge/html-report/env" 18 | helper "github.com/getgauge/html-report/test_helper" 19 | ) 20 | 21 | var now = time.Now() 22 | 23 | type testNameGenerator struct { 24 | } 25 | 26 | func (T testNameGenerator) randomName() string { 27 | return now.Format(timeFormat) 28 | } 29 | 30 | func TestGetReportsDirectory(t *testing.T) { 31 | userSetReportsDir := filepath.Join(os.TempDir(), randomName()) 32 | helper.SetEnvOrFail(t, env.GaugeReportsDirEnvName, userSetReportsDir) 33 | expectedReportsDir := filepath.Join(userSetReportsDir, htmlReport) 34 | defer func(path string) { 35 | if err := os.RemoveAll(path); err != nil { 36 | t.Errorf("Failed to remove directory %s: %v", path, err) 37 | } 38 | }(userSetReportsDir) 39 | 40 | reportsDir := getReportsDirectory(nil) 41 | 42 | if reportsDir != expectedReportsDir { 43 | t.Errorf("Expected reportsDir == %s, got: %s\n", expectedReportsDir, reportsDir) 44 | } 45 | if !helper.FileExists(expectedReportsDir) { 46 | t.Errorf("Expected %s report directory doesn't exist", expectedReportsDir) 47 | } 48 | } 49 | 50 | func TestGetReportsDirectoryWithOverrideFlag(t *testing.T) { 51 | userSetReportsDir := filepath.Join(os.TempDir(), randomName()) 52 | helper.SetEnvOrFail(t, env.GaugeReportsDirEnvName, userSetReportsDir) 53 | helper.SetEnvOrFail(t, env.OverwriteReportsEnvProperty, "true") 54 | nameGen := &testNameGenerator{} 55 | expectedReportsDir := filepath.Join(userSetReportsDir, htmlReport, nameGen.randomName()) 56 | defer func(path string) { 57 | if err := os.RemoveAll(path); err != nil { 58 | t.Errorf("Failed to remove directory %s: %v", path, err) 59 | } 60 | }(userSetReportsDir) 61 | 62 | reportsDir := getReportsDirectory(nameGen) 63 | 64 | if reportsDir != expectedReportsDir { 65 | t.Errorf("Expected reportsDir == %s, got: %s\n", expectedReportsDir, reportsDir) 66 | } 67 | if !helper.FileExists(expectedReportsDir) { 68 | t.Errorf("Expected %s report directory doesn't exist", expectedReportsDir) 69 | } 70 | } 71 | 72 | func randomName() string { 73 | return fmt.Sprintf("%d", time.Now().UnixNano()) 74 | } 75 | 76 | func TestCreatingReportShouldOverwriteReportsBasedOnEnv(t *testing.T) { 77 | helper.SetEnvOrFail(t, env.OverwriteReportsEnvProperty, "true") 78 | nameGen := getNameGen() 79 | if nameGen != nil { 80 | t.Errorf("Expected nameGen == nil, got %s", nameGen) 81 | } 82 | 83 | helper.SetEnvOrFail(t, env.OverwriteReportsEnvProperty, "false") 84 | nameGen = getNameGen() 85 | switch nameGen.(type) { 86 | case timeStampedNameGenerator: 87 | default: 88 | t.Errorf("Expected nameGen to be type timeStampedNameGenerator, got %s", reflect.TypeOf(nameGen)) 89 | } 90 | } 91 | 92 | func TestCreateReportExecutableFileShouldCreateExecFile(t *testing.T) { 93 | isSaveExecutionResultDisabled = func() bool { return false } 94 | exPath := filepath.Join(os.TempDir(), "html-report") 95 | exTargetFileName := "html-report-target" 96 | if runtime.GOOS == "windows" { 97 | exTargetFileName = "html-report-target.bat" 98 | } 99 | exTarget := filepath.Join(os.TempDir(), exTargetFileName) 100 | _, err := os.Create(exPath) 101 | if err != nil { 102 | t.Errorf("could not create %s. %s", exPath, err.Error()) 103 | } 104 | defer helper.RemoveOrFail(t, exPath) 105 | defer helper.RemoveOrFail(t, exTarget) 106 | 107 | createReportExecutableFile(exPath, exTarget) 108 | 109 | if !fileExists(exTarget) { 110 | t.Errorf("Could not create a symlink of src: %s to dst: %s", exPath, exTarget) 111 | } 112 | } 113 | func TestCreateReportExecutableFileShouldNotCreateExecFile(t *testing.T) { 114 | isSaveExecutionResultDisabled = func() bool { return true } 115 | exPath := filepath.Join(os.TempDir(), "html-report") 116 | exTarget := filepath.Join(os.TempDir(), "html-report-target") 117 | _, err := os.Create(exPath) 118 | if err != nil { 119 | t.Errorf("could not create %s. %s", exPath, err.Error()) 120 | } 121 | 122 | defer helper.RemoveOrFail(t, exPath) 123 | defer helper.RemoveOrFail(t, exTarget) 124 | defer helper.UnsetEnvOrFail(t, env.SaveExecutionResult) 125 | createReportExecutableFile(exPath, exTarget) 126 | if fileExists(exTarget) { 127 | t.Errorf("Expected not to create a symlink of src: %s to dst: %s", exPath, exTarget) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/minifiedSimpleSuiteRes/index.html: -------------------------------------------------------------------------------- 1 | Gauge Test Results

Project: Gauge Project

2 | 3 | 4 | Failed: 1/3 5 | 6 | 7 | 8 | Passed: 1/3 9 | 10 | 11 | 12 | Skipped: 1/3 13 | 14 |
Total specs3
1
1
Total scenario4
0
0
  • 15 | default
  • 16 | 60%
  • 17 | 00:02:02
  • 18 | Jul 13, 2016 at 11:49am

Generated by Gauge HTML Report

-------------------------------------------------------------------------------- /htmlReport.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package main 7 | 8 | import ( 9 | "os" 10 | "path/filepath" 11 | "time" 12 | 13 | "strings" 14 | 15 | "runtime" 16 | 17 | "github.com/getgauge/common" 18 | "github.com/getgauge/gauge-proto/go/gauge_messages" 19 | "github.com/getgauge/html-report/env" 20 | "github.com/getgauge/html-report/generator" 21 | "github.com/getgauge/html-report/logger" 22 | "github.com/getgauge/html-report/theme" 23 | ) 24 | 25 | const ( 26 | htmlReport = "html-report" 27 | setupAction = "setup" 28 | executionAction = "execution" 29 | pluginActionEnv = "html-report_action" 30 | timeFormat = "2006-01-02_15.04.05" 31 | ) 32 | 33 | type nameGenerator interface { 34 | randomName() string 35 | } 36 | 37 | type timeStampedNameGenerator struct { 38 | } 39 | 40 | func (T timeStampedNameGenerator) randomName() string { 41 | return time.Now().Format(timeFormat) 42 | } 43 | 44 | var pluginsDir string 45 | 46 | func createReport(suiteResult *gauge_messages.SuiteExecutionResult, searchIndex bool) { 47 | projectRoot, err := common.GetProjectRoot() 48 | if err != nil { 49 | logger.Debugf("Failed to generate report. %s", err.Error()) 50 | return 51 | } 52 | reportsDir := getReportsDirectory(getNameGen()) 53 | res := generator.ToSuiteResult(projectRoot, suiteResult.GetSuiteResult()) 54 | logger.Debug("Transformed SuiteResult to report structure") 55 | go createReportExecutableFile(getExecutableAndTargetPath(reportsDir, pluginsDir)) 56 | t := theme.GetThemePath(pluginsDir) 57 | generator.GenerateReport(res, reportsDir, t, searchIndex) 58 | logger.Debugf("Done generating HTML report using theme from %s", t) 59 | } 60 | 61 | func getNameGen() nameGenerator { 62 | var nameGen nameGenerator 63 | if env.ShouldOverwriteReports() { 64 | nameGen = nil 65 | } else { 66 | nameGen = timeStampedNameGenerator{} 67 | } 68 | return nameGen 69 | } 70 | 71 | func getReportsDirectory(nameGen nameGenerator) string { 72 | reportsDir, err := filepath.Abs(os.Getenv(env.GaugeReportsDirEnvName)) 73 | if reportsDir == "" || err != nil { 74 | reportsDir = env.DefaultReportsDir 75 | } 76 | env.CreateDirectory(reportsDir) 77 | var currentReportDir string 78 | if nameGen != nil { 79 | currentReportDir = filepath.Join(reportsDir, htmlReport, nameGen.randomName()) 80 | } else { 81 | currentReportDir = filepath.Join(reportsDir, htmlReport) 82 | } 83 | env.CreateDirectory(currentReportDir) 84 | return currentReportDir 85 | } 86 | 87 | func getExecutableAndTargetPath(reportsDir string, pluginsDir string) (exPath string, exTarget string) { 88 | _, bName := env.GetCurrentExecutableDir() 89 | exPath = filepath.Join(pluginsDir, "bin", bName) 90 | exTarget = filepath.Join(reportsDir, bName) 91 | return 92 | } 93 | 94 | func createReportExecutableFile(exPath, exTarget string) { 95 | if isSaveExecutionResultDisabled() { 96 | return 97 | } 98 | if fileExists(exTarget) { 99 | if err := os.Remove(exTarget); err != nil { 100 | logger.Debugf("[Warning] Unable to remove existing file %s. Reason: %s\n", exTarget, err.Error()) 101 | return 102 | } 103 | } 104 | if runtime.GOOS == "windows" { 105 | createBatFileToExecuteHTMLReport(exPath, exTarget) 106 | } else { 107 | createSymlinkToHTMLReport(exPath, exTarget) 108 | } 109 | } 110 | 111 | func createBatFileToExecuteHTMLReport(exPath, exTarget string) { 112 | content := "@echo off \n" + exPath + " %*" 113 | o := []byte(content) 114 | exTarget = strings.TrimSuffix(exTarget, filepath.Ext(exTarget)) 115 | outF := exTarget + ".bat" 116 | err := os.WriteFile(outF, o, common.NewFilePermissions) 117 | if err != nil { 118 | logger.Debugf("[Warning] Failed to write to %s. Reason: %s\n", outF, err.Error()) 119 | return 120 | } 121 | logger.Debugf("Generated %s", outF) 122 | } 123 | 124 | func createSymlinkToHTMLReport(exPath, exTarget string) { 125 | if _, err := os.Lstat(exTarget); err == nil { 126 | if err := os.Remove(exTarget); err != nil { 127 | logger.Debugf("[Warning] Unable to remove existing symlink %s\n", exTarget) 128 | return 129 | } 130 | } 131 | if err := os.Symlink(exPath, exTarget); err != nil { 132 | logger.Debugf("[Warning] Unable to create symlink %s\n", exTarget) 133 | } 134 | logger.Debugf("Generated symlink %s", exTarget) 135 | } 136 | 137 | func fileExists(path string) bool { 138 | _, err := os.Stat(path) 139 | if err == nil { 140 | return true 141 | } 142 | return !os.IsNotExist(err) 143 | } 144 | 145 | var isSaveExecutionResultDisabled = func() bool { 146 | return os.Getenv(env.SaveExecutionResult) == "false" 147 | } 148 | -------------------------------------------------------------------------------- /themes/default/assets/js/auto-complete.min.js: -------------------------------------------------------------------------------- 1 | // JavaScript autoComplete v1.0.4 2 | // https://github.com/Pixabay/JavaScript-autoComplete 3 | var autoComplete=function(){function e(e){function t(e,t){return e.classList?e.classList.contains(t):new RegExp("\\b"+t+"\\b").test(e.className)}function o(e,t,o){e.attachEvent?e.attachEvent("on"+t,o):e.addEventListener(t,o)}function s(e,t,o){e.detachEvent?e.detachEvent("on"+t,o):e.removeEventListener(t,o)}function n(e,s,n,l){o(l||document,s,function(o){for(var s,l=o.target||o.srcElement;l&&!(s=t(l,e));)l=l.parentElement;s&&n.call(l,o)})}if(document.querySelector){var l={selector:0,source:0,minChars:3,delay:150,offsetLeft:0,offsetTop:1,cache:1,menuClass:"",renderItem:function(e,t){t=t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");var o=new RegExp("("+t.split(" ").join("|")+")","gi");return'
'+e.replace(o,"$1")+"
"},onSelect:function(){}};for(var c in e)e.hasOwnProperty(c)&&(l[c]=e[c]);for(var a="object"==typeof l.selector?[l.selector]:document.querySelectorAll(l.selector),u=0;u0?i.sc.scrollTop=n+i.sc.suggestionHeight+s-i.sc.maxHeight:0>n&&(i.sc.scrollTop=n+s)}else i.sc.scrollTop=0},o(window,"resize",i.updateSC),document.body.appendChild(i.sc),n("autocomplete-suggestion","mouseleave",function(){var e=i.sc.querySelector(".autocomplete-suggestion.selected");e&&setTimeout(function(){e.className=e.className.replace("selected","")},20)},i.sc),n("autocomplete-suggestion","mouseover",function(){var e=i.sc.querySelector(".autocomplete-suggestion.selected");e&&(e.className=e.className.replace("selected","")),this.className+=" selected"},i.sc),n("autocomplete-suggestion","mousedown",function(e){if(t(this,"autocomplete-suggestion")){var o=this.getAttribute("data-val");i.value=o,l.onSelect(e,o,this),i.sc.style.display="none"}},i.sc),i.blurHandler=function(){try{var e=document.querySelector(".autocomplete-suggestions:hover")}catch(t){var e=0}e?i!==document.activeElement&&setTimeout(function(){i.focus()},20):(i.last_val=i.value,i.sc.style.display="none",setTimeout(function(){i.sc.style.display="none"},350))},o(i,"blur",i.blurHandler);var r=function(e){var t=i.value;if(i.cache[t]=e,e.length&&t.length>=l.minChars){for(var o="",s=0;st||t>40)&&13!=t&&27!=t){var o=i.value;if(o.length>=l.minChars){if(o!=i.last_val){if(i.last_val=o,clearTimeout(i.timer),l.cache){if(o in i.cache)return void r(i.cache[o]);for(var s=1;s 25 | 26 | Installation 27 | ------------ 28 | 29 | ``` 30 | gauge install html-report 31 | ``` 32 | 33 | * Installing specific version 34 | ``` 35 | gauge install html-report --version 4.3.3 36 | ``` 37 | 38 | #### Offline installation 39 | * Download the plugin from [Releases](https://github.com/getgauge/html-report/releases) 40 | ``` 41 | gauge install html-report --file html-report-4.3.3-linux.x86_64.zip 42 | ``` 43 | 44 | #### Build from Source 45 | 46 | ##### Requirements 47 | * [Golang](http://golang.org/) 48 | 49 | ##### Compiling 50 | Download dependencies 51 | ``` 52 | go get -t ./... 53 | ``` 54 | 55 | Compilation 56 | ``` 57 | 58 | go run build/make.go 59 | ``` 60 | 61 | For cross-platform compilation 62 | 63 | ``` 64 | go run build/make.go --all-platforms 65 | ``` 66 | 67 | ##### Installing 68 | After compilation 69 | 70 | ``` 71 | go run build/make.go --install 72 | ``` 73 | 74 | Installing to a CUSTOM_LOCATION 75 | 76 | ``` 77 | go run build/make.go --install --plugin-prefix CUSTOM_LOCATION 78 | ``` 79 | 80 | #### Creating distributable 81 | 82 | Note: Run after compiling 83 | 84 | ``` 85 | go run build/make.go --distro 86 | ``` 87 | 88 | For distributable across platforms: Windows and Linux for both x86 and x86_64 89 | 90 | ``` 91 | go run build/make.go --distro --all-platforms 92 | ``` 93 | 94 | New distribution details need to be updated in the `html-report-install.json` file in the [gauge plugin repository](https://github.com/getgauge/gauge-repository) for a new version update. 95 | 96 | Configuration 97 | ------------- 98 | 99 | The HTML report plugin can be configured by the properties set in the 100 | `env/default.properties` file in the project. 101 | 102 | The configurable properties are: 103 | 104 | **gauge_reports_dir** 105 | 106 | - Specifies the path to the directory where the execution reports will 107 | be generated. 108 | 109 | - Should be either relative to the project directory or an absolute 110 | path. By default it is set to `reports` directory in the project 111 | 112 | **overwrite_reports** 113 | 114 | - Set to ``true`` if the reports **must be overwritten** on each 115 | execution maintaining only the latest execution report. 116 | 117 | - If set to `false` then a _**new report**_ will be generated on each execution in the reports directory in a nested time-stamped directory. By default it is set to `true`. 118 | 119 | 120 | **GAUGE_HTML_REPORT_THEME_PATH** 121 | 122 | - Specifies the path to the custom theme directory. 123 | 124 | - Should be either relative to the project directory or an absolute 125 | path. By default, `default` theme shipped with gauge is used. 126 | 127 | **gauge_minify_reports** 128 | 129 | - Set to ``true`` if the generated HTML files needs to be minified. This helps avoid creating huge reports if the project suite is huge. 130 | 131 | Report re-generation 132 | ------------------- 133 | 134 | If report generation fails due to some reason, we don't have to re-run the tests again. 135 | 136 | Gauge now generates a last_run_result file in the `.gauge` folder under the Project Root. There is also a symlink to the html-report executable available in /html-report. 137 | 138 | **To regenerate the report** 139 | 140 | - Navigate to the reports directory 141 | - move the `html-report` file to `.gauge` directory 142 | - Navigate to the `.gauge` directory 143 | - run `./html-report --input=last_run_result --output="/some/path"` 144 | 145 | **Note:** The output directory is created. Take care not to overwrite an existing directory. The `html-report` executable and `last_run_result` will be generated only if the property `save_execution_result` is set to `true`. 146 | While regenerating a report, the default theme is used. A custom can be used if ``--theme`` flag is specified with the path to the custom theme. 147 | 148 | 149 | License 150 | ------- 151 | 152 | This program is licensed under: 153 | 154 | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) 155 | 156 | Copyright 157 | --------- 158 | 159 | Copyright 2015 ThoughtWorks, Inc. 160 | -------------------------------------------------------------------------------- /regenerate/_testdata/expectedE2E/simpleSuiteRes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Gauge Test Results 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: foo

23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 | Failed: 0/1 36 | 37 | Passed: 1/1 38 | 39 | Skipped: 0/1 40 | 41 |
42 |
43 |
44 |
45 |
Total specs1
46 |
0
47 |
1
48 | 49 |
50 |
51 |
Total scenario2
52 |
0
53 |
2
54 | 55 |
56 |
57 |
58 |
    59 |
  • 60 | 61 | default 62 |
  • 63 | 64 |
  • 65 | 66 | 100% 67 |
  • 68 |
  • 69 | 70 | 00:00:00 71 |
  • 72 |
  • 73 | 74 | May 23, 2017 at 6:01pm 75 |
  • 76 |
77 |
78 |
79 | 80 | 81 |
82 | 83 | 84 | 109 | 110 | 111 | 112 |
113 |

Congratulations! You've gone all green and saved the environment!

114 |
115 | 116 |
117 |
118 |
119 | 120 |
121 |
122 |

Generated by Gauge HTML Report

123 |
124 |
125 | 126 | 127 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/nestedSuiteRes/nested/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Gauge Test Results 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: Gauge Project

23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 | Failed: 0/1 36 | 37 | Passed: 0/1 38 | 39 | Skipped: 1/1 40 | 41 |
42 |
43 |
44 |
45 |
Total specs1
46 |
0
47 |
0
48 | 49 |
50 |
51 |
Total scenario1
52 |
0
53 |
1
54 | 55 |
56 |
57 |
58 |
    59 |
  • 60 | 61 | default 62 |
  • 63 | 64 |
  • 65 | 66 | 0% 67 |
  • 68 |
  • 69 | 70 | 00:00:00 71 |
  • 72 |
  • 73 | 74 | Jul 13, 2016 at 11:49am 75 |
  • 76 |
77 |
78 |
79 | 80 | 81 |
82 | 83 | 84 | 109 | 110 | 111 | 112 |
113 |

Congratulations! You've gone all green and saved the environment!

114 |
115 | 116 |
117 |
118 |
119 | 120 |
121 |
122 |

Generated by Gauge HTML Report

123 |
124 |
125 | 126 | 127 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/nestedSuiteRes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Gauge Test Results 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: Gauge Project

23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 | Failed: 0/2 36 | 37 | Passed: 2/2 38 | 39 | Skipped: 0/2 40 | 41 |
42 |
43 |
44 |
45 |
Total specs2
46 |
0
47 |
2
48 | 49 |
50 |
51 |
Total scenario3
52 |
0
53 |
0
54 | 55 |
56 |
57 |
58 |
    59 |
  • 60 | 61 | default 62 |
  • 63 | 64 |
  • 65 | 66 | 100% 67 |
  • 68 |
  • 69 | 70 | 00:02:02 71 |
  • 72 |
  • 73 | 74 | Jul 13, 2016 at 11:49am 75 |
  • 76 |
77 |
78 |
79 | 80 | 81 |
82 | 83 | 84 | 118 | 119 | 120 | 121 |
122 |

Congratulations! You've gone all green and saved the environment!

123 |
124 | 125 |
126 |
127 |
128 | 129 |
130 |
131 |

Generated by Gauge HTML Report

132 |
133 |
134 | 135 | 136 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/minifiedSimpleSuiteRes/skipped_specification.html: -------------------------------------------------------------------------------- 1 | Gauge Test Results

Project: Gauge Project

2 | 3 | 4 | Failed: 1/3 5 | 6 | 7 | 8 | Passed: 1/3 9 | 10 | 11 | 12 | Skipped: 1/3 13 | 14 |
Total specs3
1
1
Total scenario4
0
0
  • 15 | default
  • 16 | 60%
  • 17 | 00:02:02
  • 18 | Jul 13, 2016 at 11:49am

Skipped Specification

23 | 24 |
00:00:00

skipped scenario

00:00:00
  • Context Step
  • skipped step

Generated by Gauge HTML Report

-------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 2 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 3 | github.com/dmotylev/goproperties v0.0.0-20140630191356-7cbffbaada47 h1:sP2APvSdZpfBiousrppBZNOvu+TE79Myq4kkmmrtSuI= 4 | github.com/dmotylev/goproperties v0.0.0-20140630191356-7cbffbaada47/go.mod h1:f2V6964+f0p8Asqy8mIK5cKyyVc6MP9PFzGVNRcnYJQ= 5 | github.com/documize/html-diff v0.0.0-20160503140253-f61c192c7796 h1:CuipXymSP8DiNHYVGWak4cF2IbYFQeL5CZ37Y6aDVq4= 6 | github.com/documize/html-diff v0.0.0-20160503140253-f61c192c7796/go.mod h1:GTEVMy1JkyV+k/j8hLGRGHVs/IHJS4s7AtJJ9LSYjRQ= 7 | github.com/getgauge/common v0.0.0-20251001154240-471505c641c5 h1:jo4x8MD1bXykQckTg8TcC/5maRFkbZS1rULnUWK+lTs= 8 | github.com/getgauge/common v0.0.0-20251001154240-471505c641c5/go.mod h1:j4ycvkEuUDSM1pYN2HAwjHtT70ErW3gB/tFKIP8znfE= 9 | github.com/getgauge/gauge-proto/go/gauge_messages v0.0.0-20251009113823-9780b2b3681a h1:LTslyUJpH+W6uwQ8K54ys3lSgLhJtP+0E1cRizpOR4k= 10 | github.com/getgauge/gauge-proto/go/gauge_messages v0.0.0-20251009113823-9780b2b3681a/go.mod h1:a85iQQq5OY9RbinZ1g8d6VYwJJsL9ihktNXijwNhhYg= 11 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 12 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 13 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 14 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 15 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 16 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 17 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 18 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 19 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 20 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 21 | github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= 22 | github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= 23 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 24 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 25 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 26 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 27 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 28 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 29 | github.com/mb0/diff v0.0.0-20131118162322-d8d9a906c24d h1:eAS2t2Vy+6psf9LZ4T5WXWsbkBt3Tu5PWekJy5AGyEU= 30 | github.com/mb0/diff v0.0.0-20131118162322-d8d9a906c24d/go.mod h1:3YMHqrw2Qu3Liy82v4QdAG17e9k91HZ7w3hqlpWqhDo= 31 | github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= 32 | github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 33 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 34 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 35 | github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= 36 | github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= 37 | github.com/tdewolff/minify/v2 v2.24.7 h1:aJNQ2s0WYZg58j5ZJQo0Mk0UXMPhvCXCMHbJEgWIDXQ= 38 | github.com/tdewolff/minify/v2 v2.24.7/go.mod h1:0Ukj0CRpo/sW/nd8uZ4ccXaV1rEVIWA3dj8U7+Shhfw= 39 | github.com/tdewolff/parse/v2 v2.8.5 h1:ZmBiA/8Do5Rpk7bDye0jbbDUpXXbCdc3iah4VeUvwYU= 40 | github.com/tdewolff/parse/v2 v2.8.5/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo= 41 | github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE= 42 | github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= 43 | go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= 44 | go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= 45 | go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= 46 | go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= 47 | go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= 48 | go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= 49 | go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= 50 | go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= 51 | go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= 52 | go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= 53 | go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= 54 | go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= 55 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= 56 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= 57 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 58 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 59 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 60 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 61 | gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= 62 | gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= 63 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= 64 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= 65 | google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= 66 | google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= 67 | google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= 68 | google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 69 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 70 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 71 | -------------------------------------------------------------------------------- /generator/_testdata/integration/pass_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gauge Test Results 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: Gauge Project

23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | Failed: 0/1 35 | 36 | 37 | 38 | Passed: 1/1 39 | 40 | 41 | 42 | Skipped: 0/1 43 | 44 | 45 |
46 |
47 |
48 |
49 |
Total specs1
50 |
0
51 |
1
52 | 53 |
54 |
55 |
Total scenario0
56 |
0
57 |
0
58 | 59 |
60 |
61 |
62 |
    63 |
  • 64 | 65 | default 66 |
  • 67 |
  • 68 | 69 | 100% 70 |
  • 71 |
  • 72 | 73 | 00:02:02 74 |
  • 75 |
  • 76 | 77 | Jul 13, 2016 at 11:49am 78 |
  • 79 |
80 |
81 |
82 |
83 | 104 |
105 |

Congratulations! You've gone all green and saved the environment!

106 |
107 |
108 |
109 |
110 |
111 |
112 |

Generated by Gauge HTML Report

113 |
114 |
115 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /generator/e2e_test.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package generator 7 | 8 | import ( 9 | "os" 10 | "path/filepath" 11 | "testing" 12 | 13 | "github.com/getgauge/html-report/env" 14 | helper "github.com/getgauge/html-report/test_helper" 15 | ) 16 | 17 | var suiteRes3 = newProtoSuiteRes(true, 1, 1, 60, nil, nil, passSpecRes1, failSpecResWithStepFailure, skippedSpecRes) 18 | var suiteResWithBeforeSuiteFailure = newProtoSuiteRes(true, 0, 0, 0, newProtoHookFailure(), newProtoHookFailure()) 19 | var templateBasePath, _ = filepath.Abs(filepath.Join("..", "themes", "default")) 20 | 21 | func TestEndToEndHTMLGenerationWhenBeforeSuiteFails(t *testing.T) { 22 | reportDir := filepath.Join("_testdata", "e2e") 23 | r := ToSuiteResult("", suiteResWithBeforeSuiteFailure) 24 | err := GenerateReports(r, reportDir, templateBasePath, true) 25 | 26 | if err != nil { 27 | t.Errorf("Expected error to be nil. Got: %s", err.Error()) 28 | } 29 | gotContent, err := os.ReadFile(filepath.Join(reportDir, "index.html")) 30 | if err != nil { 31 | t.Errorf("Error reading generated HTML file: %s", err.Error()) 32 | } 33 | wantContent, err := os.ReadFile(filepath.Join("_testdata", "expectedE2E", "before_suite_fail.html")) 34 | if err != nil { 35 | t.Errorf("Error reading expected HTML file: %s", err.Error()) 36 | } 37 | got := helper.RemoveNewline(string(gotContent)) 38 | want := helper.RemoveNewline(string(wantContent)) 39 | helper.AssertEqual(want, got, "index.html", t) 40 | cleanUp(t, reportDir) 41 | } 42 | 43 | func TestEndToEndHTMLGeneration(t *testing.T) { 44 | expectedFiles := []string{"index.html", "passing_specification_1.html", "failing_specification_1.html", "skipped_specification.html", "js/search_index.js"} 45 | reportDir := filepath.Join("_testdata", "e2e") 46 | 47 | r := ToSuiteResult("", suiteRes3) 48 | err := GenerateReports(r, reportDir, templateBasePath, true) 49 | 50 | if err != nil { 51 | t.Errorf("Expected error to be nil. Got: %s", err.Error()) 52 | } 53 | 54 | verifyExpectedFiles(t, "simpleSuiteRes", reportDir, expectedFiles) 55 | cleanUp(t, reportDir) 56 | } 57 | 58 | func TestEndToEndMinifiedHTMLGeneration(t *testing.T) { 59 | expectedFiles := []string{"index.html", "passing_specification_1.html", "failing_specification_1.html", "skipped_specification.html", "js/search_index.js"} 60 | reportDir := filepath.Join("_testdata", "e2e") 61 | helper.SetEnvOrFail(t, "gauge_minify_reports", "true") 62 | r := ToSuiteResult("", suiteRes3) 63 | err := GenerateReports(r, reportDir, templateBasePath, true) 64 | 65 | if err != nil { 66 | t.Errorf("Expected error to be nil. Got: %s", err.Error()) 67 | } 68 | 69 | verifyExpectedFiles(t, "minifiedSimpleSuiteRes", reportDir, expectedFiles) 70 | helper.UnsetEnvOrFail(t, "gauge_minify_reports") 71 | cleanUp(t, reportDir) 72 | } 73 | 74 | func TestEndToEndHTMLGenerationWithPreAndPostHookScreenshots(t *testing.T) { 75 | expectedFiles := []string{"index.html", "passing_specification_1.html", "failing_specification_1.html", "skipped_specification.html", "js/search_index.js"} 76 | reportDir := filepath.Join("_testdata", "e2e") 77 | suiteRes := newProtoSuiteRes(true, 1, 1, 60, nil, nil, passSpecRes1, failSpecResWithStepFailure, skippedSpecRes) 78 | suiteRes.PreHookScreenshotFiles = []string{"pre-hook-screenshot-1.png", "pre-hook-screenshot-2.png"} 79 | suiteRes.PostHookScreenshotFiles = []string{"post-hook-screenshot-1.png", "post-hook-screenshot-2.png"} 80 | r := ToSuiteResult("", suiteRes) 81 | err := GenerateReports(r, reportDir, templateBasePath, true) 82 | 83 | if err != nil { 84 | t.Errorf("Expected error to be nil. Got: %s", err.Error()) 85 | } 86 | 87 | verifyExpectedFiles(t, "simpleSuiteResWithHookScreenshots", reportDir, expectedFiles) 88 | cleanUp(t, reportDir) 89 | } 90 | 91 | func TestEndToEndHTMLGenerationForThemeWithRelativePath(t *testing.T) { 92 | expectedFiles := []string{"index.html", "passing_specification_1.html", "failing_specification_1.html", "skipped_specification.html", "js/search_index.js"} 93 | reportDir := filepath.Join("_testdata", "e2e") 94 | defaultThemePath := filepath.Join("..", "themes", "default") 95 | 96 | r := ToSuiteResult("", suiteRes3) 97 | err := GenerateReports(r, reportDir, defaultThemePath, true) 98 | 99 | if err != nil { 100 | t.Errorf("Expected error to be nil. Got: %s", err.Error()) 101 | } 102 | 103 | verifyExpectedFiles(t, "simpleSuiteRes", reportDir, expectedFiles) 104 | cleanUp(t, reportDir) 105 | } 106 | 107 | func TestEndToEndHTMLGenerationForCustomTheme(t *testing.T) { 108 | expectedFiles := []string{"index.html", "passing_specification_1.html", "failing_specification_1.html", "skipped_specification.html", "js/search_index.js"} 109 | reportDir := filepath.Join("_testdata", "e2e") 110 | defaultThemePath := filepath.Join("_testdata", "dummyReportTheme") 111 | 112 | r := ToSuiteResult("", suiteRes3) 113 | err := GenerateReports(r, reportDir, defaultThemePath, true) 114 | 115 | if err != nil { 116 | t.Errorf("Expected error to be nil. Got: %s", err.Error()) 117 | } 118 | 119 | verifyExpectedFiles(t, "simpleSuiteRes", reportDir, expectedFiles) 120 | cleanUp(t, reportDir) 121 | } 122 | 123 | func TestEndToEndHTMLGenerationForNestedSpecs(t *testing.T) { 124 | helper.SetEnvOrFail(t, env.UseNestedSpecs, "true") 125 | var suiteRes4 = newProtoSuiteRes(false, 0, 0, 100, nil, nil, passSpecRes1, nestedSpecRes) 126 | expectedFiles := []string{ 127 | "index.html", 128 | "passing_specification_1.html", 129 | filepath.Join("nested", "nested_specification.html"), 130 | filepath.Join("nested", "index.html"), 131 | "js/search_index.js", 132 | } 133 | reportDir := filepath.Join("_testdata", "e2e") 134 | 135 | r := ToSuiteResult("", suiteRes4) 136 | err := GenerateReports(r, reportDir, templateBasePath, true) 137 | 138 | if err != nil { 139 | t.Errorf("Expected error to be nil. Got: %s", err.Error()) 140 | } 141 | verifyExpectedFiles(t, "nestedSuiteRes", reportDir, expectedFiles) 142 | cleanUp(t, reportDir) 143 | } 144 | 145 | func cleanUp(t *testing.T, reportDir string) { 146 | s, err := filepath.Glob(filepath.Join(reportDir, "*")) 147 | if err != nil { 148 | t.Error(err) 149 | } 150 | for _, f := range s { 151 | if f != filepath.Join(reportDir, ".gitkeep") { 152 | if err := os.RemoveAll(f); err != nil { 153 | t.Errorf("Failed to remove file %s: %v", f, err) 154 | } 155 | } 156 | } 157 | } 158 | 159 | func verifyExpectedFiles(t *testing.T, suiteRes, reportDir string, expectedFiles []string) { 160 | for _, expectedFile := range expectedFiles { 161 | gotContent, err := os.ReadFile(filepath.Join(reportDir, expectedFile)) 162 | if err != nil { 163 | t.Errorf("Error reading generated HTML file: %s", err.Error()) 164 | } 165 | wantContent, err := os.ReadFile(filepath.Join("_testdata", "expectedE2E", suiteRes, expectedFile)) 166 | if err != nil { 167 | t.Errorf("Error reading expected HTML file: %s", err.Error()) 168 | } 169 | got := helper.RemoveNewline(string(gotContent)) 170 | want := helper.RemoveNewline(string(wantContent)) 171 | helper.AssertEqual(want, got, expectedFile, t) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/simpleSuiteRes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gauge Test Results 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: Gauge Project

23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | Failed: 1/3 35 | 36 | 37 | 38 | Passed: 1/3 39 | 40 | 41 | 42 | Skipped: 1/3 43 | 44 | 45 |
46 |
47 |
48 |
49 |
Total specs3
50 |
1
51 |
1
52 | 53 |
54 |
55 |
Total scenario4
56 |
0
57 |
0
58 | 59 |
60 |
61 |
62 |
    63 |
  • 64 | 65 | default 66 |
  • 67 |
  • 68 | 69 | 60% 70 |
  • 71 |
  • 72 | 73 | 00:02:02 74 |
  • 75 |
  • 76 | 77 | Jul 13, 2016 at 11:49am 78 |
  • 79 |
80 |
81 |
82 |
83 | 116 |
117 |
118 |
119 |
120 |
121 |

Generated by Gauge HTML Report

122 |
123 |
124 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/minifiedSimpleSuiteRes/failing_specification_1.html: -------------------------------------------------------------------------------- 1 | Gauge Test Results

Project: Gauge Project

2 | 3 | 4 | Failed: 1/3 5 | 6 | 7 | 8 | Passed: 1/3 9 | 10 | 11 | 12 | Skipped: 1/3 13 | 14 |
Total specs3
1
1
Total scenario4
0
0
  • 15 | default
  • 16 | 60%
  • 17 | 00:02:02
  • 18 | Jul 13, 2016 at 11:49am

Failing Specification 1

23 | 24 |
00:03:31

Scenario Heading

00:01:53
Execution Time : 00:03:31
  • passing step
Execution Time : 00:03:31
  • This is a failing step

    java.lang.RuntimeException

    26 | StepImplementation.foo(StepImplementation.java:16)
    27 | sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    28 | sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    29 | sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    30 | java.lang.reflect.Method.invoke(Method.java:483)
    31 | com.thoughtworks.gauge.execution.MethodExecutor.execute(MethodExecutor.java:32)
    32 | com.thoughtworks.gauge.execution.HooksExecutor$TaggedHookExecutor.executeHook(HooksExecutor.java:98)
    33 | com.thoughtworks.gauge.execution.HooksExecutor$TaggedHookExecutor.execute(HooksExecutor.java:84)
    34 | com.thoughtworks.gauge.execution.HooksExecutor.execute(HooksExecutor.java:41)
    35 | com.thoughtworks.gauge.processor.MethodExecutionMessageProcessor.executeHooks(MethodExecutionMessageProcessor.java:55)
    36 | com.thoughtworks.gauge.processor.SuiteExecutionStartingProcessor.process(SuiteExecutionStartingProcessor.java:26)
    37 | com.thoughtworks.gauge.connection.MessageDispatcher.dispatchMessages(MessageDispatcher.java:72)
    38 | com.thoughtworks.gauge.GaugeRuntime.main(GaugeRuntime.java:37) 39 |
  • This step is skipped because previous one failed

Generated by Gauge HTML Report

-------------------------------------------------------------------------------- /generator/_testdata/integration/spec_err.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gauge Test Results 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: Gauge Project

23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | Failed: 1/1 35 | 36 | 37 | 38 | Passed: 0/1 39 | 40 | 41 | 42 | Skipped: 0/1 43 | 44 | 45 |
46 |
47 |
48 |
49 |
Total specs1
50 |
1
51 |
0
52 | 53 |
54 |
55 |
Total scenario0
56 |
0
57 |
0
58 | 59 |
60 |
61 |
62 |
    63 |
  • 64 | 65 | default 66 |
  • 67 |
  • 68 | 69 | 0% 70 |
  • 71 |
  • 72 | 73 | 00:02:02 74 |
  • 75 |
  • 76 | 77 | Jul 13, 2016 at 11:49am 78 |
  • 79 |
80 |
81 |
82 |
83 | 104 |
105 |
106 |
107 |

Error Spec

108 | 124 |
125 |
126 |
127 | 128 | 129 | 132 |
133 | 00:00:00
Tags: bar 134 |
135 |
Errors:
[Parse Error] message
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |

Generated by Gauge HTML Report

144 |
145 |
146 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/simpleSuiteResWithHookScreenshots/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gauge Test Results 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: Gauge Project

23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | Failed: 1/3 35 | 36 | 37 | 38 | Passed: 1/3 39 | 40 | 41 | 42 | Skipped: 1/3 43 | 44 | 45 |
46 |
47 |
48 |
49 |
Total specs3
50 |
1
51 |
1
52 | 53 |
54 |
55 |
Total scenario4
56 |
0
57 |
0
58 | 59 |
60 |
61 |
62 |
    63 |
  • 64 | 65 | default 66 |
  • 67 |
  • 68 | 69 | 60% 70 |
  • 71 |
  • 72 | 73 | 00:02:02 74 |
  • 75 |
  • 76 | 77 | Jul 13, 2016 at 11:49am 78 |
  • 79 |
80 |
81 |
Before Suite Screenshots
82 |
83 |
84 | 85 | 86 | 87 |
88 |
89 | 90 | 91 | 92 |
93 |
94 |
After Suite Screenshots
95 |
96 |
97 | 98 | 99 | 100 |
101 |
102 | 103 | 104 | 105 |
106 |
107 |
108 |
109 |
110 |
111 | 144 |
145 |
146 |
147 |
148 |
149 |

Generated by Gauge HTML Report

150 |
151 |
152 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /generator/_testdata/integration/before_after_suite_fail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gauge Test Results 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: Gauge Project

23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | Failed: 0/0 35 | 36 | 37 | 38 | Passed: 0/0 39 | 40 | 41 | 42 | Skipped: 0/0 43 | 44 | 45 |
46 |
47 | 0 48 | Total specs 49 |
50 |
51 |
52 |
    53 |
  • 54 | 0 55 | Failed 56 |
  • 57 |
  • 58 | 0 59 | Passed 60 |
  • 61 | 65 |
66 |
67 |
68 |
    69 |
  • 70 | 71 | default 72 |
  • 73 |
  • 74 | 75 | 0% 76 |
  • 77 |
  • 78 | 79 | 00:02:02 80 |
  • 81 |
  • 82 | 83 | Jul 13, 2016 at 11:49am 84 |
  • 85 |
86 |
87 |
88 |
89 |
Before Suite Failed: 90 | java.lang.RuntimeException 91 |
92 |
93 | [Show details] 94 |
95 |
96 |
97 |
 98 | StepImplementation.foo(StepImplementation.java:16)
99 | sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
100 | sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
101 | sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
102 | java.lang.reflect.Method.invoke(Method.java:483)
103 | com.thoughtworks.gauge.execution.MethodExecutor.execute(MethodExecutor.java:32)
104 | com.thoughtworks.gauge.execution.HooksExecutor$TaggedHookExecutor.executeHook(HooksExecutor.java:98)
105 | com.thoughtworks.gauge.execution.HooksExecutor$TaggedHookExecutor.execute(HooksExecutor.java:84)
106 | com.thoughtworks.gauge.execution.HooksExecutor.execute(HooksExecutor.java:41)
107 | com.thoughtworks.gauge.processor.MethodExecutionMessageProcessor.executeHooks(MethodExecutionMessageProcessor.java:55)
108 | com.thoughtworks.gauge.processor.SuiteExecutionStartingProcessor.process(SuiteExecutionStartingProcessor.java:26)
109 | com.thoughtworks.gauge.connection.MessageDispatcher.dispatchMessages(MessageDispatcher.java:72)
110 | com.thoughtworks.gauge.GaugeRuntime.main(GaugeRuntime.java:37) 111 |
112 |
113 |
114 |
115 | 116 | 117 | 118 |
119 |
120 |
121 |
122 |
123 |
After Suite Failed: 124 | java.lang.RuntimeException 125 |
126 |
127 | [Show details] 128 |
129 |
130 |
131 |
132 | StepImplementation.foo(StepImplementation.java:16)
133 | sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
134 | sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
135 | sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
136 | java.lang.reflect.Method.invoke(Method.java:483)
137 | com.thoughtworks.gauge.execution.MethodExecutor.execute(MethodExecutor.java:32)
138 | com.thoughtworks.gauge.execution.HooksExecutor$TaggedHookExecutor.executeHook(HooksExecutor.java:98)
139 | com.thoughtworks.gauge.execution.HooksExecutor$TaggedHookExecutor.execute(HooksExecutor.java:84)
140 | com.thoughtworks.gauge.execution.HooksExecutor.execute(HooksExecutor.java:41)
141 | com.thoughtworks.gauge.processor.MethodExecutionMessageProcessor.executeHooks(MethodExecutionMessageProcessor.java:55)
142 | com.thoughtworks.gauge.processor.SuiteExecutionStartingProcessor.process(SuiteExecutionStartingProcessor.java:26)
143 | com.thoughtworks.gauge.connection.MessageDispatcher.dispatchMessages(MessageDispatcher.java:72)
144 | com.thoughtworks.gauge.GaugeRuntime.main(GaugeRuntime.java:37) 145 |
146 |
147 |
148 |
149 | 150 | 151 | 152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |

Generated by Gauge HTML Report

161 |
162 |
163 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /generator/_testdata/expectedE2E/before_suite_fail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gauge Test Results 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: Gauge Project

23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | Failed: 0/0 35 | 36 | 37 | 38 | Passed: 0/0 39 | 40 | 41 | 42 | Skipped: 0/0 43 | 44 | 45 |
46 |
47 |
48 |
49 |
Total specs0
50 |
0
51 |
0
52 | 53 |
54 |
55 |
Total scenario0
56 |
0
57 |
0
58 | 59 |
60 |
61 |
62 |
    63 |
  • 64 | 65 | default 66 |
  • 67 |
  • 68 | 69 | 0% 70 |
  • 71 |
  • 72 | 73 | 00:02:02 74 |
  • 75 |
  • 76 | 77 | Jul 13, 2016 at 11:49am 78 |
  • 79 |
80 |
81 |
82 |
83 |
Before Suite Failed: 84 | java.lang.RuntimeException 85 |
86 |
87 | [Show details] 88 |
89 | 115 |
116 |
117 |
After Suite Failed: 118 | java.lang.RuntimeException 119 |
120 |
121 | [Show details] 122 |
123 | 149 |
150 |
151 |
152 |
153 |
154 |

Generated by Gauge HTML Report

155 |
156 |
157 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /generator/transform_multiline_test.go: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------- 2 | * Copyright (c) ThoughtWorks, Inc. 3 | * Licensed under the Apache License, Version 2.0 4 | * See LICENSE in the project root for license information. 5 | *----------------------------------------------------------------*/ 6 | package generator 7 | 8 | import ( 9 | "testing" 10 | 11 | gm "github.com/getgauge/gauge-proto/go/gauge_messages" 12 | ) 13 | 14 | // Multiline string test helpers 15 | func newMultilineStringParam(name, value string) *gm.Parameter { 16 | return &gm.Parameter{ 17 | Name: name, 18 | ParameterType: gm.Parameter_Special_String, 19 | Value: value, 20 | } 21 | } 22 | 23 | func newMultilineParamFragment(name, value string) *gm.Fragment { 24 | return &gm.Fragment{ 25 | FragmentType: gm.Fragment_Parameter, 26 | Parameter: newMultilineStringParam(name, value), 27 | } 28 | } 29 | 30 | // Test data for multiline steps 31 | var protoStepWithMultilineString = &gm.ProtoStep{ 32 | Fragments: []*gm.Fragment{ 33 | newTextFragment("Step with JSON "), 34 | newMultilineParamFragment("file:data.json", "{\n \"name\": \"Gauge\",\n \"type\": \"Testing\"\n}"), 35 | }, 36 | StepExecutionResult: &gm.ProtoStepExecutionResult{ 37 | ExecutionResult: &gm.ProtoExecutionResult{ 38 | Failed: false, 39 | ExecutionTime: 211316, 40 | }, 41 | }, 42 | } 43 | 44 | var protoStepWithMixedMultiline = &gm.ProtoStep{ 45 | Fragments: []*gm.Fragment{ 46 | newTextFragment("Step with "), 47 | newMultilineParamFragment("file:config.txt", "key=value\nanother=setting"), 48 | newTextFragment(" and "), 49 | newMultilineParamFragment("file:simple.txt", "simple value"), 50 | }, 51 | StepExecutionResult: &gm.ProtoStepExecutionResult{ 52 | ExecutionResult: &gm.ProtoExecutionResult{ 53 | Failed: false, 54 | ExecutionTime: 211316, 55 | }, 56 | }, 57 | } 58 | 59 | var protoStepWithEmptyMultiline = &gm.ProtoStep{ 60 | Fragments: []*gm.Fragment{ 61 | newTextFragment("Step with empty content "), 62 | newMultilineParamFragment("file:empty.txt", ""), 63 | }, 64 | StepExecutionResult: &gm.ProtoStepExecutionResult{ 65 | ExecutionResult: &gm.ProtoExecutionResult{ 66 | Failed: false, 67 | ExecutionTime: 211316, 68 | }, 69 | }, 70 | } 71 | 72 | // Test cases for multiline string support 73 | func TestToFragmentsWithMultilineStringParameter(t *testing.T) { 74 | multilineContent := "line1\nline2\nline3" 75 | protoFragments := []*gm.Fragment{ 76 | newMultilineParamFragment("file:multiline.txt", multilineContent), 77 | } 78 | 79 | want := []*fragment{ 80 | { 81 | FragmentKind: multilineFragmentKind, 82 | Text: multilineContent, 83 | }, 84 | } 85 | 86 | got := toFragments(protoFragments) 87 | checkEqual(t, "Multiline string parameter", want, got) 88 | } 89 | 90 | func TestToFragmentsWithSingleLineStringParameter(t *testing.T) { 91 | singleLineContent := "single line value" 92 | protoFragments := []*gm.Fragment{ 93 | newMultilineParamFragment("file:sample.txt", singleLineContent), 94 | } 95 | 96 | want := []*fragment{ 97 | { 98 | FragmentKind: specialStringFragmentKind, 99 | Name: "file:sample.txt", 100 | Text: singleLineContent, 101 | FileName: "sample.txt", 102 | }, 103 | } 104 | 105 | got := toFragments(protoFragments) 106 | checkEqual(t, "Single line string parameter", want, got) 107 | } 108 | 109 | func TestToFragmentsWithMixedTypesIncludingMultiline(t *testing.T) { 110 | multilineContent := "key=value\nanother=setting" 111 | protoFragments := []*gm.Fragment{ 112 | newTextFragment("Step with "), 113 | newMultilineParamFragment("file:config.txt", multilineContent), 114 | newTextFragment(" and "), 115 | newMultilineParamFragment("file:simple.txt", "simple value"), 116 | } 117 | 118 | want := []*fragment{ 119 | {FragmentKind: textFragmentKind, Text: "Step with "}, 120 | {FragmentKind: multilineFragmentKind, Text: multilineContent}, 121 | {FragmentKind: textFragmentKind, Text: " and "}, 122 | {FragmentKind: specialStringFragmentKind, Name: "file:simple.txt", Text: "simple value", FileName: "simple.txt"}, 123 | } 124 | 125 | got := toFragments(protoFragments) 126 | checkEqual(t, "Mixed types including multiline", want, got) 127 | } 128 | 129 | func TestToStepWithMultilineStringParameter(t *testing.T) { 130 | want := &step{ 131 | Fragments: []*fragment{ 132 | {FragmentKind: textFragmentKind, Text: "Step with JSON "}, 133 | {FragmentKind: multilineFragmentKind, Text: "{\n \"name\": \"Gauge\",\n \"type\": \"Testing\"\n}"}, 134 | }, 135 | Result: &result{ 136 | Status: pass, 137 | ExecutionTime: "00:03:31", 138 | }, 139 | } 140 | 141 | got := toStep(protoStepWithMultilineString) 142 | checkEqual(t, "Step with multiline string parameter", want, got) 143 | } 144 | 145 | func TestToStepWithMixedMultilineAndSingleLine(t *testing.T) { 146 | want := &step{ 147 | Fragments: []*fragment{ 148 | {FragmentKind: textFragmentKind, Text: "Step with "}, 149 | {FragmentKind: multilineFragmentKind, Text: "key=value\nanother=setting"}, 150 | {FragmentKind: textFragmentKind, Text: " and "}, 151 | {FragmentKind: specialStringFragmentKind, Name: "file:simple.txt", Text: "simple value", FileName: "simple.txt"}, 152 | }, 153 | Result: &result{ 154 | Status: pass, 155 | ExecutionTime: "00:03:31", 156 | }, 157 | } 158 | 159 | got := toStep(protoStepWithMixedMultiline) 160 | checkEqual(t, "Step with mixed multiline and single line", want, got) 161 | } 162 | 163 | func TestToStepWithEmptyMultilineString(t *testing.T) { 164 | want := &step{ 165 | Fragments: []*fragment{ 166 | {FragmentKind: textFragmentKind, Text: "Step with empty content "}, 167 | {FragmentKind: specialStringFragmentKind, Name: "file:empty.txt", Text: "", FileName: "empty.txt"}, 168 | }, 169 | Result: &result{ 170 | Status: pass, 171 | ExecutionTime: "00:03:31", 172 | }, 173 | } 174 | 175 | got := toStep(protoStepWithEmptyMultiline) 176 | checkEqual(t, "Step with empty multiline string", want, got) 177 | } 178 | 179 | func TestToFragmentsWithMultilineStringContainingOnlyNewline(t *testing.T) { 180 | protoFragments := []*gm.Fragment{ 181 | newMultilineParamFragment("file:newline.txt", "\n"), 182 | } 183 | 184 | want := []*fragment{ 185 | { 186 | FragmentKind: multilineFragmentKind, 187 | Text: "\n", 188 | }, 189 | } 190 | 191 | got := toFragments(protoFragments) 192 | checkEqual(t, "Multiline string with only newline", want, got) 193 | } 194 | 195 | func TestToFragmentsWithComplexMultilineContent(t *testing.T) { 196 | complexContent := `# Configuration File 197 | database: 198 | host: localhost 199 | port: 5432 200 | name: gauge_db 201 | 202 | logging: 203 | level: info 204 | file: /var/log/gauge.log` 205 | 206 | protoFragments := []*gm.Fragment{ 207 | newTextFragment("Load configuration "), 208 | newMultilineParamFragment("file:config.yaml", complexContent), 209 | } 210 | 211 | want := []*fragment{ 212 | {FragmentKind: textFragmentKind, Text: "Load configuration "}, 213 | {FragmentKind: multilineFragmentKind, Text: complexContent}, 214 | } 215 | 216 | got := toFragments(protoFragments) 217 | checkEqual(t, "Complex multiline content", want, got) 218 | } 219 | 220 | func TestToFragmentsWithXMLMultilineContent(t *testing.T) { 221 | xmlContent := ` 222 | John Doe 223 | john@example.com 224 | 225 | admin 226 | user 227 | 228 | ` 229 | 230 | protoFragments := []*gm.Fragment{ 231 | newTextFragment("Create user with XML "), 232 | newMultilineParamFragment("file:user.xml", xmlContent), 233 | } 234 | 235 | want := []*fragment{ 236 | {FragmentKind: textFragmentKind, Text: "Create user with XML "}, 237 | {FragmentKind: multilineFragmentKind, Text: xmlContent}, 238 | } 239 | 240 | got := toFragments(protoFragments) 241 | checkEqual(t, "XML multiline content", want, got) 242 | } 243 | 244 | func TestToFragmentsWithCodeSnippetMultiline(t *testing.T) { 245 | codeContent := `func main() { 246 | fmt.Println("Hello, World!") 247 | for i := 0; i < 10; i++ { 248 | fmt.Printf("Count: %d\n", i) 249 | } 250 | }` 251 | 252 | protoFragments := []*gm.Fragment{ 253 | newTextFragment("Execute code: "), 254 | newMultilineParamFragment("file:main.go", codeContent), 255 | } 256 | 257 | want := []*fragment{ 258 | {FragmentKind: textFragmentKind, Text: "Execute code: "}, 259 | {FragmentKind: multilineFragmentKind, Text: codeContent}, 260 | } 261 | 262 | got := toFragments(protoFragments) 263 | checkEqual(t, "Code snippet multiline content", want, got) 264 | } 265 | 266 | // Test edge case: string with Windows-style line endings 267 | func TestToFragmentsWithWindowsLineEndings(t *testing.T) { 268 | windowsContent := "line1\r\nline2\r\nline3" 269 | protoFragments := []*gm.Fragment{ 270 | newMultilineParamFragment("file:windows.txt", windowsContent), 271 | } 272 | 273 | want := []*fragment{ 274 | { 275 | FragmentKind: multilineFragmentKind, 276 | Text: windowsContent, 277 | }, 278 | } 279 | 280 | got := toFragments(protoFragments) 281 | checkEqual(t, "Windows line endings", want, got) 282 | } 283 | 284 | // Test edge case: string with mixed line endings 285 | func TestToFragmentsWithMixedLineEndings(t *testing.T) { 286 | mixedContent := "line1\nline2\r\nline3" 287 | protoFragments := []*gm.Fragment{ 288 | newMultilineParamFragment("file:mixed.txt", mixedContent), 289 | } 290 | 291 | want := []*fragment{ 292 | { 293 | FragmentKind: multilineFragmentKind, 294 | Text: mixedContent, 295 | }, 296 | } 297 | 298 | got := toFragments(protoFragments) 299 | checkEqual(t, "Mixed line endings", want, got) 300 | } -------------------------------------------------------------------------------- /themes/default/assets/js/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v1.5.12 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,o){function i(a,c){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!c&&s)return s(a,!0);if(r)return r(a,!0);var l=new Error("Cannot find module '"+a+"'");throw l.code="MODULE_NOT_FOUND",l}var u=n[a]={exports:{}};e[a][0].call(u.exports,function(t){var n=e[a][1][t];return i(n?n:t)},u,u.exports,t,e,n,o)}return n[a].exports}for(var r="function"==typeof require&&require,a=0;ao;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],i=[];if(o&&e)for(var r=0,a=o.length;a>r;r++)o[r].fn!==e&&o[r].fn._!==e&&i.push(o[r]);return i.length?n[t]=i:delete n[t],this}},e.exports=o},{}],8:[function(e,n,o){!function(i,r){if("function"==typeof t&&t.amd)t(["module","select"],r);else if("undefined"!=typeof o)r(n,e("select"));else{var a={exports:{}};r(a,i.select),i.clipboardAction=a.exports}}(this,function(t,e){"use strict";function n(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var i=n(e),r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol?"symbol":typeof t},a=function(){function t(t,e){for(var n=0;n 2 | 3 | 4 | 5 | 6 | 7 | Gauge Test Results 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: Gauge Project

23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | Failed: 0/1 34 | 35 | 36 | 37 | Passed: 1/1 38 | 39 | 40 | 41 | Skipped: 0/1 42 | 43 | 44 |
45 |
46 |
47 |
48 |
Total specs1
49 |
0
50 |
1
51 | 52 |
53 |
54 |
Total scenario1
55 |
0
56 |
0
57 | 58 |
59 |
60 |
61 |
    62 |
  • 63 | default
  • 64 |
  • 65 | 100%
  • 66 |
  • 67 | 00:02:02
  • 68 |
  • 69 | Jul 13, 2016 at 11:49am
  • 70 |
71 |
72 |
73 |
74 | 91 |
92 |
93 |
94 |

Specification 1 with custom screenshots

95 | 102 |
103 |
104 |
105 | 106 | 107 | 108 |
00:03:31
109 |
110 |
111 |
112 |
113 |
114 |

Scenario Heading

00:01:53
115 |
116 |
Execution Time : 00:03:31
117 |
118 |
    119 |
  • 120 |
    This is a step with custom screenshot
    121 |
    122 |
    123 | 124 |
    125 |
    126 | 127 |
    128 |
    129 |
  • 130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |

Generated by Gauge HTML Report

143 |
144 |
145 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /themes/default/assets/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | -------------------------------------------------------------------------------- /generator/_testdata/integration/skipped_spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Gauge Test Results 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 22 |

Project: Gauge Project

23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | Failed: 0/1 35 | 36 | 37 | 38 | Passed: 0/1 39 | 40 | 41 | 42 | Skipped: 1/1 43 | 44 | 45 |
46 |
47 |
48 |
49 |
Total specs1
50 |
0
51 |
0
52 | 53 |
54 |
55 |
Total scenario1
56 |
0
57 |
0
58 | 59 |
60 |
61 |
62 |
    63 |
  • 64 | 65 | default 66 |
  • 67 |
  • 68 | 69 | 0% 70 |
  • 71 |
  • 72 | 73 | 00:02:02 74 |
  • 75 |
  • 76 | 77 | Jul 13, 2016 at 11:49am 78 |
  • 79 |
80 |
81 |
82 |
83 | 104 |
105 |
106 |
107 |

Skipped Specification

108 | 124 |
125 |
126 |
127 | 128 | 129 | 132 |
133 | 00:00:00 134 |
135 |
136 |
137 |
138 |
139 |
140 |

skipped scenario

141 | 00:00:00 142 |
143 |
144 |
145 |
146 |
    147 |
  • 148 |
    149 | 150 | Context Step 151 | 152 |
    153 |
  • 154 |
155 |
156 |
157 |
158 |
159 |
160 |
    161 |
  • 162 |
    163 | skipped step 164 |
    165 |
  • 166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |

Generated by Gauge HTML Report

179 |
180 |
181 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | --------------------------------------------------------------------------------