integration/ui: add UI integration tests

Introduce a new `integration/ui` package for integration tests.

`integration/ui/uitest` contains a helper package for running browser tests.
This commit is contained in:
andriikotko 2021-06-09 14:39:38 +03:00 committed by GitHub
parent 962f81433f
commit 072dd24696
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 255 additions and 0 deletions

78
Jenkinsfile.ui Normal file
View File

@ -0,0 +1,78 @@
pipeline {
agent {
docker {
label 'main'
image docker.build("storj-ci", "--pull git://github.com/storj/ci.git#main").id
args '-u root:root --cap-add SYS_PTRACE -v "/tmp/gomod":/go/pkg/mod -v "/tmp/npm":/npm --tmpfs "/tmp:exec,mode=777"'
}
}
options {
timeout(time: 36, unit: 'MINUTES')
}
environment {
NPM_CONFIG_CACHE = '/npm/cache'
GOTRACEBACK = 'all'
COCKROACH_MEMPROF_INTERVAL=0
}
stages {
stage('Build') {
steps {
checkout scm
sh 'mkdir -p .build'
sh 'go mod download'
sh 'service postgresql start'
dir(".build") {
sh 'cockroach start-single-node --insecure --store=type=mem,size=2GiB --listen-addr=localhost:26256 --http-addr=localhost:8086 --cache 512MiB --max-sql-memory 512MiB --background'
}
}
}
stage('Verification') {
stage('UI') {
environment {
STORJ_TEST_COCKROACH = 'cockroach://root@localhost:26256/testui?sslmode=disable'
STORJ_TEST_POSTGRES = 'postgres://postgres@localhost/testui?sslmode=disable'
STORJ_TEST_BROWSER = '/usr/bin/chromium'
STORJ_TEST_SATELLITE_WEB = "${pwd()}/.build/satellite-web"
DISPLAY = ':99'
}
steps {
sh 'cockroach sql --insecure --host=localhost:26256 -e \'create database testui;\''
sh 'psql -U postgres -c \'create database testui;\''
sh 'cp -r ./web/satellite/ ${STORJ_TEST_SATELLITE_WEB}/'
// TODO: this is not quite correct
sh 'mkdir ${STORJ_TEST_SATELLITE_WEB}/wasm'
sh 'cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ${STORJ_TEST_SATELLITE_WEB}/wasm/wasm_exec.js'
sh 'GOOS=js GOARCH=wasm go build -o ${STORJ_TEST_SATELLITE_WEB}/wasm/main.wasm storj.io/storj/satellite/console/wasm'
sh 'cd .build/satellite-web && npm install'
sh 'cd .build/satellite-web && npm run build'
sh 'Xvfb -ac :99 -screen 0 1280x1024x16 &'
sh 'go test -vet=off -race -json ./integration/ui/... 2>&1 | tee .build/ui-tests.json | xunit -out .build/ui-tests.xml'
}
post {
always {
sh script: 'cat .build/ui-tests.json | tparse -all -top -slow 100', returnStatus: true
archiveArtifacts artifacts: '.build/ui-tests.json'
junit '.build/ui-tests.xml'
}
}
}
}
}
}
post {
always {
sh "chmod -R 777 ." // ensure Jenkins agent can delete the working directory
deleteDir()
}
}
}

1
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/cheggaaa/pb/v3 v3.0.5
github.com/fatih/color v1.9.0
github.com/go-redis/redis/v8 v8.7.1
github.com/go-rod/rod v0.100.0
github.com/gogo/protobuf v1.3.2
github.com/google/go-cmp v0.5.4
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3 // indirect

12
go.sum
View File

@ -125,6 +125,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis/v8 v8.7.1 h1:8IYi6RO83fNcG5amcUUYTN/qH2h4OjZHlim3KWGFSsA=
github.com/go-redis/redis/v8 v8.7.1/go.mod h1:BRxHBWn3pO3CfjyX6vAoyeRmCquvxr6QG+2onGV2gYs=
github.com/go-rod/rod v0.100.0 h1:tEKIb5wS3pGUpW4oJPYDxOKmRXaZbd6S+YVjJ6BHBBY=
github.com/go-rod/rod v0.100.0/go.mod h1:h9igqSGReLmOWyHtdf0AtUd0mdkHFu3gFwBeV+stleM=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@ -508,6 +510,16 @@ github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3/go.mod h1:R0Gbuw
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
github.com/ysmood/goob v0.3.0 h1:XZ51cZJ4W3WCoCiUktixzMIQF86W7G5VFL4QQ/Q2uS0=
github.com/ysmood/goob v0.3.0/go.mod h1:S3lq113Y91y1UBf1wj1pFOxeahvfKkCk6mTWTWbDdWs=
github.com/ysmood/got v0.12.0 h1:Ol4cpy6Xdq1KCjPlWSA+tvekrnt9cV6LIw+Jvx0dj4M=
github.com/ysmood/got v0.12.0/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY=
github.com/ysmood/gotrace v0.2.2 h1:006KHGRThSRf8lwh4EyhNmuuq/l+Ygs+JqojkhEG1/E=
github.com/ysmood/gotrace v0.2.2/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
github.com/ysmood/gson v0.6.4 h1:Yb6tosv6bk59HqjZu2/7o4BFherpYEMkDkXmlhgryZ4=
github.com/ysmood/gson v0.6.4/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
github.com/ysmood/leakless v0.7.0 h1:XCGdaPExyoreoQd+H5qgxM3ReNbSPFsEXpSKwbXbwQw=
github.com/ysmood/leakless v0.7.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=

View File

@ -0,0 +1,33 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package satellite_test
import (
"testing"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/input"
"github.com/stretchr/testify/require"
"storj.io/common/testcontext"
"storj.io/storj/integration/ui/uitest"
"storj.io/storj/private/testplanet"
)
func TestLoginToAccount(t *testing.T) {
uitest.Run(t, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet, browser *rod.Browser) {
loginPageURL := planet.Satellites[0].ConsoleURL() + "/login"
user := planet.Uplinks[0].User[planet.Satellites[0].ID()]
page := browser.Timeout(10 * time.Second).MustPage(loginPageURL)
page.MustSetViewport(1350, 600, 1, false)
page.MustElement(".headerless-input").MustInput(user.Email)
page.MustElement("[type=password]").MustInput(user.Password)
page.Keyboard.MustPress(input.Enter)
dashboardTitle := page.MustElement(".dashboard-area__header-wrapper__title").MustText()
require.Contains(t, dashboardTitle, "Dashboard")
})
}

View File

@ -0,0 +1,92 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package uitest
import (
"fmt"
"os"
"testing"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
"github.com/go-rod/rod/lib/utils"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
"storj.io/common/testcontext"
"storj.io/storj/private/testplanet"
"storj.io/storj/satellite"
)
// Test defines common services for uitests.
type Test func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet, browser *rod.Browser)
type zapWriter struct {
*zap.Logger
}
func (log zapWriter) Write(data []byte) (int, error) {
log.Logger.Info(string(data))
return len(data), nil
}
// Run starts a new UI test.
func Run(t *testing.T, test Test) {
if os.Getenv("STORJ_TEST_SATELLITE_WEB") == "" {
t.Skip("Enable UI tests by setting STORJ_TEST_SATELLITE_WEB to built npm")
}
if os.Getenv("STORJ_TEST_SATELLITE_WEB") == "omit" {
return
}
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 4, UplinkCount: 1,
Reconfigure: testplanet.Reconfigure{
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
config.Console.StaticDir = os.Getenv("STORJ_TEST_SATELLITE_WEB")
},
},
NonParallel: true,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
showBrowser := os.Getenv("STORJ_TEST_SHOW_BROWSER") != ""
logLauncher := zaptest.NewLogger(t).Named("launcher")
launch := launcher.New().
Headless(!showBrowser).
Leakless(false).
Devtools(false).
NoSandbox(true).
UserDataDir(ctx.Dir("browser")).
Logger(zapWriter{Logger: logLauncher})
if browserBin := os.Getenv("STORJ_TEST_BROWSER"); browserBin != "" {
launch = launch.Bin(browserBin)
}
defer launch.Cleanup()
url, err := launch.Launch()
require.NoError(t, err)
logBrowser := zaptest.NewLogger(t).Named("rod")
browser := rod.New().
Timeout(time.Minute).
ControlURL(url).
SlowMotion(300 * time.Millisecond).
Logger(utils.Log(func(msg ...interface{}) {
logBrowser.Info(fmt.Sprintln(msg...))
})).
Context(ctx).
WithPanic(func(v interface{}) { require.Fail(t, "check failed", v) })
defer ctx.Check(browser.Close)
require.NoError(t, browser.Connect())
test(t, ctx, planet, browser)
})
}

View File

@ -0,0 +1,20 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package uitest_test
import (
"testing"
"github.com/go-rod/rod"
"storj.io/common/testcontext"
"storj.io/storj/integration/ui/uitest"
"storj.io/storj/private/testplanet"
)
func TestRun(t *testing.T) {
uitest.Run(t, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet, browser *rod.Browser) {
t.Log("working")
})
}

View File

@ -185,6 +185,11 @@ func (system *Satellite) Addr() string { return system.API.Server.Addr().String(
// URL returns the node url from the Satellite system API.
func (system *Satellite) URL() string { return system.NodeURL().String() }
// ConsoleURL returns the console URL.
func (system *Satellite) ConsoleURL() string {
return "http://" + system.API.Console.Listener.Addr().String()
}
// NodeURL returns the storj.NodeURL from the Satellite system API.
func (system *Satellite) NodeURL() storj.NodeURL {
return storj.NodeURL{ID: system.API.ID(), Address: system.API.Addr()}

View File

@ -21,6 +21,7 @@ func TestSatellite_AddProject(t *testing.T) {
user, err := planet.Satellites[0].AddUser(ctx, console.CreateUser{
FullName: "test user",
Email: "test-email@test",
Password: "password",
}, 4)
require.NoError(t, err)

View File

@ -39,6 +39,7 @@ type Uplink struct {
APIKey map[storj.NodeID]*macaroon.APIKey
Access map[storj.NodeID]*uplink.Access
User map[storj.NodeID]UserLogin
// Projects is indexed by the satellite number.
Projects []*Project
@ -63,6 +64,12 @@ type ProjectOwner struct {
Email string
}
// UserLogin contains information about the user login.
type UserLogin struct {
Email string
Password string
}
// DialMetainfo dials the satellite with the appropriate api key.
func (project *Project) DialMetainfo(ctx context.Context) (*metaclient.Client, error) {
return project.client.DialMetainfo(ctx, project.Satellite, project.RawAPIKey)
@ -106,6 +113,7 @@ func (planet *Planet) newUplink(ctx context.Context, name string) (*Uplink, erro
Identity: identity,
APIKey: map[storj.NodeID]*macaroon.APIKey{},
Access: map[storj.NodeID]*uplink.Access{},
User: map[storj.NodeID]UserLogin{},
}
planetUplink.Log.Debug("id=" + identity.ID.String())
@ -124,6 +132,11 @@ func (planet *Planet) newUplink(ctx context.Context, name string) (*Uplink, erro
return nil, err
}
planetUplink.User[satellite.ID()] = UserLogin{
Email: user.Email,
Password: user.FullName,
}
project, err := satellite.AddProject(ctx, user.ID, projectName)
if err != nil {
return nil, err