From d74c9257fa7c2e9493e8b2a7cb6c04539561ab36 Mon Sep 17 00:00:00 2001 From: Rustam Gilyazov <16064414+rusq@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:55:11 +1000 Subject: [PATCH] add workaround for node not being executable --- auth/browser/client.go | 72 ++++++++++++++++++++++++++++++++- auth/browser/client_test.go | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/auth/browser/client.go b/auth/browser/client.go index 2ea543ab..f5cbed40 100644 --- a/auth/browser/client.go +++ b/auth/browser/client.go @@ -6,7 +6,10 @@ import ( "fmt" "net/http" "net/url" + "os" + "path/filepath" "regexp" + "runtime" "runtime/trace" "strings" "time" @@ -26,7 +29,9 @@ type Client struct { var Logger logger.Interface = logger.Default -var installFn = playwright.Install +var ( + installFn = playwright.Install +) // New create new browser based client. func New(workspace string, opts ...Option) (*Client, error) { @@ -40,7 +45,12 @@ func New(workspace string, opts ...Option) (*Client, error) { if err := installFn(&playwright.RunOptions{ Browsers: []string{cl.br.String()}, }); err != nil { - return nil, err + if !strings.Contains(err.Error(), "could not run driver") || runtime.GOOS == "windows" { + return nil, err + } + if err := pwRepair(cl.br.String()); err != nil { + return nil, err + } } return cl, nil } @@ -205,3 +215,61 @@ func l() logger.Interface { } return Logger } + +// newDriverFn is the function that creates a new driver. It is set to +// playwright.NewDriver by default, but can be overridden for testing. +var newDriverFn = playwright.NewDriver + +// pwRepair attempts to repair the playwright installation. +func pwRepair(browser string) error { + if browser == "" { + return nil + } + drv, err := newDriverFn(&playwright.RunOptions{ + Browsers: []string{browser}, + }) + if err != nil { + return err + } + + // check node permissions + if err := pwIsKnownProblem(drv.DriverDirectory); err != nil { + return err + } + if err := os.RemoveAll(drv.DriverDirectory); err != nil { + return err + } + + // attempt to reinstall + if err := installFn(&playwright.RunOptions{ + Browsers: []string{browser}, + }); err != nil { + // we did everything we could, but it still failed. + return err + } + return nil +} + +var errUnknownProblem = errors.New("unknown problem") + +// pwIsKnownProblem checks if the playwright installation is in a known +// problematic state, and if yes, return nil. If the problem is unknown, +// returns an errUnknownProblem. +func pwIsKnownProblem(path string) error { + if runtime.GOOS == "windows" { + // this should not ever happen on windows, as this problem relates to + // executable flag not being set, which is not a thing in a + // DOS/Windows world. + return errors.New("impossible has just happened, call the exorcist") + } + fi, err := os.Stat(filepath.Join(path, "node")) + if err != nil { + return err + } + // check if the file is executable, and if yes, return an error, because + // we wouldn't know what to do. + if fi.Mode()&0111 != 0 { + return errUnknownProblem + } + return nil +} diff --git a/auth/browser/client_test.go b/auth/browser/client_test.go index 5701f5da..f62d3444 100644 --- a/auth/browser/client_test.go +++ b/auth/browser/client_test.go @@ -1,9 +1,16 @@ package browser import ( + "errors" + "io/fs" + "os" + "path/filepath" "reflect" + "runtime" "testing" "time" + + "github.com/playwright-community/playwright-go" ) func Test_extractToken(t *testing.T) { @@ -57,3 +64,76 @@ func Test_float2time(t *testing.T) { }) } } + +func Test_pwRepair(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on windows") + } + t.Run("known executable permissions problem causes reinstall", func(t *testing.T) { + baseDir := t.TempDir() + fakePwDir := filepath.Join(baseDir, "playwright-99.20.0") + + // installCalledi should be set to true if the install function is + // called. + installCalled := false + // set the mock install functions. + oldInstall := installFn + defer func() { installFn = oldInstall }() + installFn = func(...*playwright.RunOptions) error { + installCalled = true + return nil + } + oldNewDriverFn := newDriverFn + defer func() { newDriverFn = oldNewDriverFn }() + newDriverFn = func(*playwright.RunOptions) (*playwright.PlaywrightDriver, error) { + return &playwright.PlaywrightDriver{ + DriverDirectory: fakePwDir, + }, nil + } + + // create a fake node file with the wrong permissions. + makeFakeNode(t, fakePwDir, 0o644) + // run the repair function. + if err := pwRepair(baseDir); err != nil { + t.Fatal(err) + } + + if !installCalled { + t.Fatal("install was not called") + } + // check that the directory was removed + if _, err := os.Stat(fakePwDir); !os.IsNotExist(err) { + t.Fatal("directory was not removed") + } + }) +} + +func makeFakeNode(t *testing.T, dir string, mode fs.FileMode) { + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "node"), []byte("hello"), mode); err != nil { + t.Fatal(err) + } +} + +func Test_pwIsKnownProblem(t *testing.T) { + t.Run("known executable permissions problem", func(t *testing.T) { + baseDir := t.TempDir() + makeFakeNode(t, baseDir, 0o644) + if err := pwIsKnownProblem(baseDir); err != nil { + t.Fatal(err) + } + }) + t.Run("other problem", func(t *testing.T) { + baseDir := t.TempDir() + makeFakeNode(t, baseDir, 0o755) + err := pwIsKnownProblem(baseDir) + if err == nil { + t.Fatal("unexpected success") + } + if !errors.Is(err, errUnknownProblem) { + t.Fatal("unexpected error") + } + }) +}