Skip to content

Commit b8f74a8

Browse files
test(e2e): add Playwright E2E tests
Implements end-to-end tests using Playwright for UI testing: - Add E2E test infrastructure with Playwright - Create docker-compose.e2e.yml for UI testing environment - Add homepage tests validating ShellHub UI loads correctly
1 parent c2d0e7c commit b8f74a8

24 files changed

+1243
-67
lines changed

ssh/server/channels/session.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ func DefaultSessionHandler() gliderssh.ChannelHandler {
290290
reject(nil, "failed to recover the session dimensions")
291291
}
292292

293+
logger.WithFields(log.Fields{
294+
"width": dimensions.Width,
295+
"height": dimensions.Height,
296+
}).Debug("window-change request received from client")
297+
293298
sess.Event(req.Type, dimensions, seat) //nolint:errcheck
294299
case AuthRequestOpenSSHRequest:
295300
gliderssh.SetAgentRequested(ctx)

tests/docker-compose.e2e.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
version: "3.7"
2+
3+
services:
4+
ssh:
5+
image: ssh:test
6+
build:
7+
context: .
8+
dockerfile: ssh/Dockerfile
9+
target: production
10+
healthcheck:
11+
interval: 5s
12+
start_period: 10s
13+
retries: 20
14+
ports: []
15+
api:
16+
image: api:test
17+
build:
18+
context: .
19+
dockerfile: api/Dockerfile
20+
target: production
21+
healthcheck:
22+
interval: 5s
23+
start_period: 10s
24+
retries: 20
25+
ports: []
26+
gateway:
27+
image: gateway:test
28+
build:
29+
context: .
30+
dockerfile: gateway/Dockerfile
31+
target: production
32+
healthcheck:
33+
interval: 5s
34+
start_period: 10s
35+
retries: 20
36+
ports: []
37+
ui:
38+
image: ui:test
39+
build:
40+
context: .
41+
dockerfile: ui/Dockerfile
42+
target: production
43+
healthcheck:
44+
interval: 5s
45+
start_period: 10s
46+
retries: 20
47+
ports: []
48+
mongo:
49+
image: mongo:4.4.29
50+
healthcheck:
51+
test: 'test $$(echo "rs.initiate({ _id: ''rs'', members: [ { _id: 0, host: ''mongo:27017'' } ] }).ok || rs.status().ok" | mongo --quiet) -eq 1'
52+
interval: 5s
53+
start_period: 10s
54+
retries: 20
55+
command: ["--replSet", "rs", "--bind_ip_all"]
56+
ports: []
57+
redis:
58+
image: redis
59+
command: ["redis-server", "--appendonly", "no", "--save", "\"\""]
60+
ports: []
61+
62+
secrets:
63+
ssh_private_key:
64+
file: ./tests/ssh_private_key
65+
api_private_key:
66+
file: ./tests/api_private_key
67+
api_public_key:
68+
file: ./tests/api_public_key
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ services:
66
build:
77
context: .
88
dockerfile: ssh/Dockerfile
9-
target: production
9+
target: production
1010
healthcheck:
1111
interval: 5s
1212
start_period: 10s
@@ -17,7 +17,7 @@ services:
1717
build:
1818
context: .
1919
dockerfile: api/Dockerfile
20-
target: production
20+
target: production
2121
healthcheck:
2222
interval: 5s
2323
start_period: 10s
@@ -28,7 +28,7 @@ services:
2828
build:
2929
context: .
3030
dockerfile: gateway/Dockerfile
31-
target: production
31+
target: production
3232
healthcheck:
3333
interval: 5s
3434
start_period: 10s
@@ -40,3 +40,11 @@ services:
4040
start_period: 10s
4141
retries: 20
4242
ports: []
43+
44+
secrets:
45+
ssh_private_key:
46+
file: ./tests/ssh_private_key
47+
api_private_key:
48+
file: ./tests/api_private_key
49+
api_public_key:
50+
file: ./tests/api_public_key

tests/e2e/.dockerignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
node_modules/
2+
test-results/
3+
playwright-report/
4+
blob-report/
5+
playwright/.cache/
6+
*.png
7+
.git/
8+
.gitignore
9+
README.md

tests/e2e/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules/
2+
/test-results/
3+
/playwright-report/
4+
/blob-report/
5+
/playwright/.cache/
6+
*.png

tests/e2e/Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM mcr.microsoft.com/playwright:v1.57.0-jammy
2+
3+
WORKDIR /app
4+
5+
# Copy package files
6+
COPY package.json ./
7+
8+
# Install dependencies
9+
RUN npm install
10+
11+
# Copy test files and config
12+
COPY . .
13+
14+
# Default command runs tests
15+
CMD ["npm", "test"]

tests/e2e/e2e_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
"io"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
"time"
10+
11+
"github.com/docker/docker/api/types/container"
12+
"github.com/docker/docker/api/types/mount"
13+
"github.com/shellhub-io/shellhub/tests/environment"
14+
"github.com/stretchr/testify/require"
15+
tc "github.com/testcontainers/testcontainers-go"
16+
"github.com/testcontainers/testcontainers-go/wait"
17+
)
18+
19+
func TestE2E(t *testing.T) {
20+
ctx := context.Background()
21+
22+
// Start ShellHub environment with UI (using e2e compose)
23+
compose := environment.NewE2E(t).Up(ctx)
24+
t.Cleanup(compose.Down)
25+
26+
// Get the base URL for Playwright - use gateway hostname inside Docker network
27+
baseURL := "http://gateway:80"
28+
29+
// Run Playwright tests in container
30+
t.Run("homepage", func(t *testing.T) {
31+
runPlaywrightTest(t, ctx, compose, baseURL, "tests/homepage.spec.ts")
32+
})
33+
}
34+
35+
func runPlaywrightTest(t *testing.T, ctx context.Context, compose *environment.DockerCompose, baseURL, testFile string) {
36+
// Get absolute path to e2e directory
37+
absPath, err := filepath.Abs(".")
38+
require.NoError(t, err)
39+
40+
// Get the ShellHub network name from compose environment
41+
networkName := compose.Env("SHELLHUB_NETWORK")
42+
43+
// Create Playwright container that installs deps and runs tests
44+
req := tc.ContainerRequest{
45+
Image: "mcr.microsoft.com/playwright:v1.57.0-jammy",
46+
Cmd: []string{
47+
"sh", "-c",
48+
"npm install && npx playwright test " + testFile,
49+
},
50+
Env: map[string]string{
51+
"BASE_URL": baseURL,
52+
},
53+
HostConfigModifier: func(hc *container.HostConfig) {
54+
hc.Mounts = []mount.Mount{
55+
{
56+
Type: mount.TypeBind,
57+
Source: absPath,
58+
Target: "/app",
59+
},
60+
}
61+
},
62+
WorkingDir: "/app",
63+
Networks: []string{networkName},
64+
WaitingFor: wait.ForExit().WithExitTimeout(5 * time.Minute),
65+
}
66+
67+
container, err := tc.GenericContainer(ctx, tc.GenericContainerRequest{
68+
ContainerRequest: req,
69+
Started: true,
70+
})
71+
require.NoError(t, err)
72+
73+
// Wait for completion
74+
_, err = container.State(ctx)
75+
require.NoError(t, err)
76+
77+
// Get and print logs
78+
logs, err := container.Logs(ctx)
79+
require.NoError(t, err)
80+
81+
buf := new(strings.Builder)
82+
_, _ = io.Copy(buf, logs)
83+
t.Logf("Playwright output:\n%s", buf.String())
84+
85+
// Cleanup
86+
exitCode, err := container.State(ctx)
87+
require.NoError(t, err)
88+
89+
_ = container.Terminate(ctx)
90+
91+
// Check exit code
92+
require.Equal(t, 0, exitCode.ExitCode, "Playwright tests should pass")
93+
}
Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
package main
1+
package e2e
22

33
import (
4+
"context"
45
"crypto/rand"
56
"crypto/rsa"
67
"crypto/x509"
78
"encoding/pem"
89
"os"
910
"testing"
1011

12+
"github.com/shellhub-io/shellhub/tests/environment"
1113
log "github.com/sirupsen/logrus"
1214
)
1315

@@ -80,16 +82,30 @@ func keygen() error {
8082
}
8183

8284
func TestMain(m *testing.M) {
83-
// INFO: Due to issue related on testcontainers-go, we are disabling Ryuk it as a temporary solution.
85+
// INFO: Due to issue related on testcontainers-go, we are disabling Ryuk as a temporary solution.
86+
// We implement our own cleanup mechanism in environment/cleanup.go to handle resource cleanup.
8487
//
8588
// https://github.com/testcontainers/testcontainers-go/issues/2445
86-
os.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true")
89+
if environment.ShouldDisableRyuk() {
90+
os.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true")
91+
}
92+
93+
// Initialize cleanup system
94+
environment.InitCleanup()
8795

8896
if err := keygen(); err != nil {
8997
log.WithError(err).Error("failed to generate the ShellHub keys")
90-
9198
os.Exit(1)
9299
}
93100

94-
os.Exit(m.Run())
101+
// Run tests
102+
exitCode := m.Run()
103+
104+
// Cleanup any orphaned containers
105+
ctx := context.Background()
106+
if err := environment.CleanupOrphanedContainers(ctx); err != nil {
107+
log.WithError(err).Warn("failed to cleanup orphaned containers")
108+
}
109+
110+
os.Exit(exitCode)
95111
}

0 commit comments

Comments
 (0)