Skip to content

Run playwright test on CMX node #2206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 0 additions & 92 deletions api/types/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -224,94 +223,3 @@ func TestAPIError_ErrorOrNil(t *testing.T) {
})
}
}

func TestAPIError_JSON(t *testing.T) {
tests := []struct {
name string
apiErr *APIError
wantCode int
wantJSON map[string]any
}{
{
name: "simple error",
apiErr: &APIError{
StatusCode: http.StatusInternalServerError,
Message: "invalid request",
},
wantCode: http.StatusInternalServerError,
wantJSON: map[string]any{
"status_code": float64(http.StatusInternalServerError),
"message": "invalid request",
},
},
{
name: "field error",
apiErr: &APIError{
StatusCode: http.StatusBadRequest,
Message: "validation error",
Field: "username",
},
wantCode: http.StatusBadRequest,
wantJSON: map[string]any{
"status_code": float64(http.StatusBadRequest),
"message": "validation error",
"field": "username",
},
},
{
name: "error with nested errors",
apiErr: &APIError{
StatusCode: http.StatusBadRequest,
Message: "multiple validation errors",
Errors: []*APIError{
{
Message: "field1 is required",
Field: "field1",
},
{
Message: "field2 must be a number",
Field: "field2",
},
},
},
wantCode: http.StatusBadRequest,
wantJSON: map[string]any{
"status_code": float64(http.StatusBadRequest),
"message": "multiple validation errors",
"errors": []any{
map[string]any{
"message": "field1 is required",
"field": "field1",
},
map[string]any{
"message": "field2 must be a number",
"field": "field2",
},
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a mock HTTP response recorder
rec := httptest.NewRecorder()

// Call the JSON method
tt.apiErr.JSON(rec)

// Check status code
assert.Equal(t, tt.wantCode, rec.Code, "Status code should match")

// Check content type header
contentType := rec.Header().Get("Content-Type")
assert.Equal(t, "application/json", contentType, "Content-Type header should be application/json")

// Parse and check the JSON response
var gotJSON map[string]any
err := json.Unmarshal(rec.Body.Bytes(), &gotJSON)
assert.NoError(t, err, "Should be able to parse the JSON response")
assert.Equal(t, tt.wantJSON, gotJSON, "JSON response should match expected structure")
})
}
}
128 changes: 64 additions & 64 deletions e2e/cluster/cmx/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ type Node struct {
ID string `json:"id"`
Name string `json:"name"`

privateIP string `json:"-"`
sshEndpoint string `json:"-"`
adminConsoleURL string `json:"-"`
privateIP string `json:"-"`
sshEndpoint string `json:"-"`
}

type Network struct {
Expand Down Expand Up @@ -154,21 +153,16 @@ func NewNode(in *ClusterInput, index int, networkID string) (*Node, error) {
}
node.privateIP = privateIP

if err := ensureAssetsDir(node); err != nil {
if err := ensureTestDirs(node); err != nil {
return nil, fmt.Errorf("ensure assets dir on node %s: %v", node.Name, err)
}

if err := copyScriptsToNode(node); err != nil {
return nil, fmt.Errorf("copy scripts to node %s: %v", node.Name, err)
}

if index == 0 {
in.T.Logf("exposing port 30003 on node %s", node.Name)
hostname, err := exposePort(node, "30003")
if err != nil {
return nil, fmt.Errorf("expose port: %v", err)
}
node.adminConsoleURL = fmt.Sprintf("http://%s", hostname)
if err := copyPlaywrightToNode(node); err != nil {
return nil, fmt.Errorf("copy playwright to node %s: %v", node.Name, err)
}

return &node, nil
Expand All @@ -189,8 +183,8 @@ func discoverPrivateIP(node Node) (string, error) {
return "", fmt.Errorf("find private ip starting with 10.")
}

func ensureAssetsDir(node Node) error {
stdout, stderr, err := runCommandOnNode(node, []string{"mkdir", "-p", "/assets"})
func ensureTestDirs(node Node) error {
stdout, stderr, err := runCommandOnNode(node, []string{"mkdir", "-p", "/assets", "/automation/playwright"})
if err != nil {
return fmt.Errorf("create directory: %v: %s: %s", err, stdout, stderr)
}
Expand Down Expand Up @@ -232,6 +226,47 @@ func copyScriptsToNode(node Node) error {
return nil
}

func copyPlaywrightToNode(node Node) error {
// Create a temporary directory for the archive
tempDir, err := os.MkdirTemp("", "playwright-archive")
if err != nil {
return fmt.Errorf("create temp directory: %v", err)
}
defer os.RemoveAll(tempDir)

// Create the archive, excluding node_modules, test-results, and playwright-report
archivePath := filepath.Join(tempDir, "playwright.tgz")
output, err := exec.Command("tar",
"--exclude=node_modules",
"--exclude=test-results",
"--exclude=playwright-report",
"-czf", archivePath,
"-C", "playwright", ".",
).CombinedOutput()
if err != nil {
return fmt.Errorf("create playwright archive: %v: %s", err, string(output))
}

// Copy the archive to the node
if err := copyFileToNode(node, archivePath, "/tmp/playwright.tgz"); err != nil {
return fmt.Errorf("copy playwright archive to node: %v", err)
}

// Extract the archive in /automation
_, stderr, err := runCommandOnNode(node, []string{"tar", "-xzf", "/tmp/playwright.tgz", "-C", "/automation/playwright"})
if err != nil {
return fmt.Errorf("extract playwright archive: %v: %s", err, stderr)
}

// Clean up the archive on the node
_, stderr, err = runCommandOnNode(node, []string{"rm", "-f", "/tmp/playwright.tgz"})
if err != nil {
return fmt.Errorf("clean up playwright archive: %v: %s", err, stderr)
}

return nil
}

func getSSHEndpoint(nodeID string) (string, error) {
output, err := exec.Command("replicated", "vm", "ssh-endpoint", nodeID).CombinedOutput()
if err != nil {
Expand Down Expand Up @@ -353,43 +388,35 @@ func runCommandOnNode(node Node, line []string, envs ...map[string]string) (stri
return stdout.String(), stderr.String(), err
}

func (c *Cluster) SetupPlaywrightAndRunTest(testName string, args ...string) (string, string, error) {
if err := c.SetupPlaywright(); err != nil {
return "", "", fmt.Errorf("setup playwright: %w", err)
}
return c.RunPlaywrightTest(testName, args...)
}

func (c *Cluster) SetupPlaywright(envs ...map[string]string) error {
func (c *Cluster) BypassKurlProxy(envs ...map[string]string) error {
c.t.Logf("%s: bypassing kurl-proxy", time.Now().Format(time.RFC3339))
_, stderr, err := c.RunCommandOnNode(0, []string{"bypass-kurl-proxy.sh"}, envs...)
if err != nil {
return fmt.Errorf("bypass kurl-proxy: %v: %s", err, string(stderr))
}
c.t.Logf("%s: installing playwright", time.Now().Format(time.RFC3339))
output, err := exec.Command("sh", "-c", "cd playwright && npm ci && npx playwright install --with-deps").CombinedOutput()
if err != nil {
return fmt.Errorf("install playwright: %v: %s", err, string(output))
return nil
}

func (c *Cluster) SetupPlaywright(envs ...map[string]string) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why BypassKurlProxy isnt part of SetupPlaywright?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, because we need to setup playwright before airgapping the nodes as it needs internet access and kubernetes (kubectl) isn't available in at that point so bypassing kurl proxy has to happen separately.

c.t.Logf("%s: installing playwright on node 0", time.Now().Format(time.RFC3339))
if _, stderr, err := c.RunCommandOnNode(0, []string{"install-playwright.sh"}); err != nil {
return fmt.Errorf("install playwright on node 0: %v: %s", err, string(stderr))
}
return nil
}

func (c *Cluster) RunPlaywrightTest(testName string, args ...string) (string, string, error) {
c.t.Logf("%s: running playwright test %s", time.Now().Format(time.RFC3339), testName)
cmdArgs := []string{testName}
cmdArgs = append(cmdArgs, args...)
cmd := exec.Command("scripts/playwright.sh", cmdArgs...)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, fmt.Sprintf("BASE_URL=%s", c.Nodes[0].adminConsoleURL))
cmd.Env = append(cmd.Env, "PLAYWRIGHT_DIR=./playwright")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
envs := map[string]string{
"BASE_URL": "http://localhost:30003",
"PLAYWRIGHT_DIR": "/automation/playwright",
}
line := append([]string{"playwright.sh", testName}, args...)
stdout, stderr, err := c.RunCommandOnNode(0, line, envs)
if err != nil {
return stdout.String(), stderr.String(), fmt.Errorf("run playwright test %s: %v", testName, err)
return stdout, stderr, fmt.Errorf("run playwright test %s: %v", testName, err)
}
return stdout.String(), stderr.String(), nil
return stdout, stderr, nil
}

func (c *Cluster) generateSupportBundle(envs ...map[string]string) {
Expand Down Expand Up @@ -442,33 +469,6 @@ func (c *Cluster) copyPlaywrightReport() {
}
}

func exposePort(node Node, port string) (string, error) {
output, err := exec.Command("replicated", "vm", "port", "expose", node.ID, "--port", port).CombinedOutput()
if err != nil {
return "", fmt.Errorf("expose port: %v: %s", err, string(output))
}

output, err = exec.Command("replicated", "vm", "port", "ls", node.ID, "-ojson").Output() // stderr can break json parsing
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
return "", fmt.Errorf("get port info: %w: stderr: %s: stdout: %s", err, string(exitErr.Stderr), string(output))
}
return "", fmt.Errorf("get port info: %w: stdout: %s", err, string(output))
}

var ports []struct {
Hostname string `json:"hostname"`
}
if err := json.Unmarshal(output, &ports); err != nil {
return "", fmt.Errorf("unmarshal port info: %v", err)
}

if len(ports) == 0 {
return "", fmt.Errorf("no ports found for node %s", node.ID)
}
return ports[0].Hostname, nil
}

func copyFileToNode(node Node, src, dst string) error {
scpEndpoint := strings.Replace(node.sshEndpoint, "ssh://", "scp://", 1)

Expand Down
13 changes: 5 additions & 8 deletions e2e/cluster/docker/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,16 @@ func (c *Cluster) RunCommandOnNode(node int, line []string, envs ...map[string]s
return c.Nodes[node].Exec(line, envs...)
}

func (c *Cluster) SetupPlaywrightAndRunTest(testName string, args ...string) (string, string, error) {
if err := c.SetupPlaywright(); err != nil {
return "", "", fmt.Errorf("failed to setup playwright: %w", err)
}
return c.RunPlaywrightTest(testName, args...)
}

func (c *Cluster) SetupPlaywright(envs ...map[string]string) error {
func (c *Cluster) BypassKurlProxy(envs ...map[string]string) error {
c.t.Logf("%s: bypassing kurl-proxy", time.Now().Format(time.RFC3339))
_, stderr, err := c.RunCommandOnNode(0, []string{"bypass-kurl-proxy.sh"}, envs...)
if err != nil {
return fmt.Errorf("fail to bypass kurl-proxy: %v: %s", err, string(stderr))
}
return nil
}

func (c *Cluster) SetupPlaywright(envs ...map[string]string) error {
c.t.Logf("%s: installing playwright", time.Now().Format(time.RFC3339))
cmd := exec.Command("sh", "-c", "cd playwright && npm ci && npx playwright install --with-deps")
out, err := cmd.CombinedOutput()
Expand Down
2 changes: 1 addition & 1 deletion e2e/cluster/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type Cluster interface {

RunCommandOnNode(node int, line []string, envs ...map[string]string) (string, string, error)

SetupPlaywrightAndRunTest(testName string, args ...string) (string, string, error)
BypassKurlProxy(envs ...map[string]string) error
SetupPlaywright(envs ...map[string]string) error
RunPlaywrightTest(testName string, args ...string) (string, string, error)
}
15 changes: 6 additions & 9 deletions e2e/cluster/lxd/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -1136,20 +1136,17 @@ func (c *Cluster) Cleanup(envs ...map[string]string) {
c.copyPlaywrightReport()
}

func (c *Cluster) SetupPlaywrightAndRunTest(testName string, args ...string) (string, string, error) {
if err := c.SetupPlaywright(); err != nil {
return "", "", fmt.Errorf("failed to setup playwright: %w", err)
}
return c.RunPlaywrightTest(testName, args...)
}

func (c *Cluster) SetupPlaywright(envs ...map[string]string) error {
func (c *Cluster) BypassKurlProxy(envs ...map[string]string) error {
c.T.Logf("%s: bypassing kurl-proxy on node 0", time.Now().Format(time.RFC3339))
line := []string{"bypass-kurl-proxy.sh"}
if _, stderr, err := c.RunCommandOnNode(0, line, envs...); err != nil {
return fmt.Errorf("fail to bypass kurl-proxy on node %s: %v: %s", c.Nodes[0], err, string(stderr))
}
line = []string{"install-playwright.sh"}
return nil
}

func (c *Cluster) SetupPlaywright(envs ...map[string]string) error {
line := []string{"install-playwright.sh"}
c.T.Logf("%s: installing playwright on proxy node", time.Now().Format(time.RFC3339))
if _, stderr, err := c.RunCommandOnProxyNode(c.T, line); err != nil {
return fmt.Errorf("fail to install playwright on node %s: %v: %s", c.Proxy, err, string(stderr))
Expand Down
Loading
Loading