github.com/kubeshop/testkube@v1.17.23/contrib/executor/playwright/pkg/runner/playwright.go (about)

     1  package runner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/pkg/errors"
    10  
    11  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    12  	"github.com/kubeshop/testkube/pkg/envs"
    13  	"github.com/kubeshop/testkube/pkg/executor"
    14  	"github.com/kubeshop/testkube/pkg/executor/agent"
    15  	"github.com/kubeshop/testkube/pkg/executor/env"
    16  	"github.com/kubeshop/testkube/pkg/executor/output"
    17  	"github.com/kubeshop/testkube/pkg/executor/runner"
    18  	"github.com/kubeshop/testkube/pkg/executor/scraper"
    19  	"github.com/kubeshop/testkube/pkg/executor/scraper/factory"
    20  	"github.com/kubeshop/testkube/pkg/ui"
    21  )
    22  
    23  func NewPlaywrightRunner(ctx context.Context, dependency string, params envs.Params) (*PlaywrightRunner, error) {
    24  	output.PrintLogf("%s Preparing test runner", ui.IconTruck)
    25  
    26  	var err error
    27  	r := &PlaywrightRunner{
    28  		Params:     params,
    29  		dependency: dependency,
    30  	}
    31  
    32  	r.Scraper, err = factory.TryGetScrapper(ctx, params)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	return r, nil
    38  }
    39  
    40  // PlaywrightRunner - implements runner interface used in worker to start test execution
    41  type PlaywrightRunner struct {
    42  	Params     envs.Params
    43  	Scraper    scraper.Scraper
    44  	dependency string
    45  }
    46  
    47  var _ runner.Runner = &PlaywrightRunner{}
    48  
    49  func (r *PlaywrightRunner) Run(ctx context.Context, execution testkube.Execution) (result testkube.ExecutionResult, err error) {
    50  	if r.Scraper != nil {
    51  		defer r.Scraper.Close()
    52  	}
    53  	output.PrintLogf("%s Preparing for test run", ui.IconTruck)
    54  
    55  	// check that the datadir exists
    56  	_, err = os.Stat(r.Params.DataDir)
    57  	if errors.Is(err, os.ErrNotExist) {
    58  		output.PrintLogf("%s Datadir %s does not exist", ui.IconCross, r.Params.DataDir)
    59  		return result, errors.Errorf("datadir not exist: %v", err)
    60  	}
    61  
    62  	runPath := filepath.Join(r.Params.DataDir, "repo", execution.Content.Repository.Path)
    63  	if execution.Content.Repository != nil && execution.Content.Repository.WorkingDir != "" {
    64  		runPath = filepath.Join(r.Params.DataDir, "repo", execution.Content.Repository.WorkingDir)
    65  	}
    66  
    67  	if _, err := os.Stat(filepath.Join(runPath, "package.json")); err == nil {
    68  		out, err := executor.Run(runPath, r.dependency, nil, "install")
    69  		if err != nil {
    70  			output.PrintLogf("%s Dependency installation error %s", ui.IconCross, r.dependency)
    71  			return result, errors.Errorf("%s install error: %v\n\n%s", r.dependency, err, out)
    72  		}
    73  		output.PrintLogf("%s Dependencies successfully installed", ui.IconBox)
    74  	}
    75  
    76  	var depManager, depCommand string
    77  	if r.dependency == "pnpm" {
    78  		depManager = "pnpm"
    79  		depCommand = "dlx"
    80  	} else {
    81  		depManager = "npx"
    82  	}
    83  
    84  	args := execution.Args
    85  	for i := range execution.Command {
    86  		if execution.Command[i] == "<depManager>" {
    87  			execution.Command[i] = depManager
    88  		}
    89  	}
    90  
    91  	for i := len(args) - 1; i >= 0; i-- {
    92  		if depCommand == "" && args[i] == "<depCommand>" {
    93  			args = append(args[:i], args[i+1:]...)
    94  			continue
    95  		}
    96  
    97  		if args[i] == "<depCommand>" {
    98  			args[i] = depCommand
    99  		}
   100  
   101  		args[i] = os.ExpandEnv(args[i])
   102  	}
   103  
   104  	envManager := env.NewManagerWithVars(execution.Variables)
   105  	envManager.GetReferenceVars(envManager.Variables)
   106  
   107  	command, args := executor.MergeCommandAndArgs(execution.Command, args)
   108  	output.PrintEvent("Running", runPath, command, envManager.ObfuscateStringSlice(args))
   109  	out, runErr := executor.Run(runPath, command, envManager, args...)
   110  	out = envManager.ObfuscateSecrets(out)
   111  
   112  	if runErr != nil {
   113  		output.PrintLogf("%s Test run failed", ui.IconCross)
   114  		result = testkube.ExecutionResult{
   115  			Status:     testkube.ExecutionStatusFailed,
   116  			OutputType: "text/plain",
   117  			Output:     fmt.Sprintf("playwright test error: %s\n\n%s", runErr.Error(), out),
   118  		}
   119  	} else {
   120  		result = testkube.ExecutionResult{
   121  			Status:     testkube.ExecutionStatusPassed,
   122  			OutputType: "text/plain",
   123  			Output:     string(out),
   124  		}
   125  	}
   126  
   127  	var rerr error
   128  	if execution.PostRunScript != "" && execution.ExecutePostRunScriptBeforeScraping {
   129  		output.PrintLog(fmt.Sprintf("%s Running post run script...", ui.IconCheckMark))
   130  
   131  		if runPath == "" {
   132  			runPath = r.Params.WorkingDir
   133  		}
   134  
   135  		if rerr = agent.RunScript(execution.PostRunScript, runPath); rerr != nil {
   136  			output.PrintLogf("%s Failed to execute post run script %s", ui.IconWarning, rerr)
   137  		}
   138  	}
   139  
   140  	if r.Params.ScrapperEnabled {
   141  		reportFile := "playwright-report"
   142  		if err = scrapeArtifacts(ctx, r, execution, reportFile); err != nil {
   143  			return result, err
   144  		}
   145  	}
   146  
   147  	if rerr != nil {
   148  		return result, rerr
   149  	}
   150  
   151  	if runErr == nil {
   152  		output.PrintLogf("%s Test run successful", ui.IconCheckMark)
   153  	}
   154  	return result, runErr
   155  }
   156  
   157  // GetType returns runner type
   158  func (r *PlaywrightRunner) GetType() runner.Type {
   159  	return runner.TypeMain
   160  }
   161  
   162  func scrapeArtifacts(ctx context.Context, r *PlaywrightRunner, execution testkube.Execution, reportName string) (err error) {
   163  	projectPath := filepath.Join(r.Params.DataDir, "repo", execution.Content.Repository.Path)
   164  
   165  	compressedName := reportName + "-zip"
   166  	if _, err := executor.Run(projectPath, "mkdir", nil, compressedName); err != nil {
   167  		output.PrintLogf("%s Artifact scraping failed: making dir %s", ui.IconCross, compressedName)
   168  		return errors.Errorf("mkdir error: %v", err)
   169  	}
   170  
   171  	if _, err := executor.Run(projectPath, "zip", nil, compressedName+"/"+reportName+".zip", "-r", reportName); err != nil {
   172  		output.PrintLogf("%s Artifact scraping failed: zipping reports %s", ui.IconCross, reportName)
   173  		return errors.Errorf("zip error: %v", err)
   174  	}
   175  
   176  	directories := []string{
   177  		filepath.Join(projectPath, compressedName),
   178  	}
   179  
   180  	var masks []string
   181  	if execution.ArtifactRequest != nil {
   182  		directories = append(directories, execution.ArtifactRequest.Dirs...)
   183  		masks = execution.ArtifactRequest.Masks
   184  	}
   185  
   186  	output.PrintLogf("Scraping directories: %v with masks: %v", directories, masks)
   187  
   188  	if err := r.Scraper.Scrape(ctx, directories, masks, execution); err != nil {
   189  		return errors.Wrap(err, "error scraping artifacts")
   190  	}
   191  
   192  	return nil
   193  }