playwright-go实战:自动化登录测试
1.新建项目
打开Goland新建项目playwright-go-demo
项目初始化完成后打开终端输入命令:
#安装项目依赖
go get -u github.com/playwright-community/playwright-go
#安装浏览器
go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps
2.编写代码
项目结构
部分代码
config.go
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
// BrowserConfig 浏览器配置
type BrowserConfig struct {
Type string `json:"type"` // 浏览器类型:chromium, firefox, webkit
Headless bool `json:"headless"` // 是否无头模式
SlowMo int `json:"slowMo"` // 慢动作模式,毫秒
Maximized bool `json:"maximized"` // 是否最大化
}
// LoginConfig 登录配置
type LoginConfig struct {
Username string `json:"username"` // 用户名
Password string `json:"password"` // 密码
URL string `json:"url"` // 登录URL
InvalidUsername string `json:"invalid_username"` // 无效用户名
InvalidPassword string `json:"invalid_password"` // 无效密码
}
// Config 应用配置
type Config struct {
Browsers []BrowserConfig `json:"browsers"` // 多浏览器配置
Login LoginConfig `json:"login"` // 登录配置
}
// DefaultConfig 默认配置
var DefaultConfig = Config{
Browsers: []BrowserConfig{
{
Type: "chromium",
Headless: false,
SlowMo: 0,
Maximized: true,
},
{
Type: "webkit",
Headless: false,
SlowMo: 0,
Maximized: true,
},
},
Login: LoginConfig{
Username: "tomsmith",
Password: "SuperSecretPassword!",
URL: "http://the-internet.herokuapp.com/login",
InvalidUsername: "invaliduser",
InvalidPassword: "invalidpass",
},
}
// LoadConfig 从文件加载配置
func LoadConfig(configPath string) (*Config, error) {
// 如果配置文件不存在,创建默认配置文件
if _, err := os.Stat(configPath); os.IsNotExist(err) {
// 确保目录存在
dir := filepath.Dir(configPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("无法创建配置目录: %w", err)
}
// 写入默认配置
file, err := os.Create(configPath)
if err != nil {
return nil, fmt.Errorf("无法创建配置文件: %w", err)
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
if err := encoder.Encode(DefaultConfig); err != nil {
return nil, fmt.Errorf("无法写入默认配置: %w", err)
}
return &DefaultConfig, nil
}
// 读取配置文件
file, err := os.Open(configPath)
if err != nil {
return nil, fmt.Errorf("无法打开配置文件: %w", err)
}
defer file.Close()
config := &Config{}
if err := json.NewDecoder(file).Decode(config); err != nil {
return nil, fmt.Errorf("无法解析配置文件: %w", err)
}
return config, nil
}
config.json
{
"browsers": [
{
"type": "chromium",
"headless": true,
"slowMo": 0,
"maximized": true
},
{
"type": "webkit",
"headless": true,
"slowMo": 0,
"maximized": true
}
],
"login": {
"username": "tomsmith",
"password": "SuperSecretPassword!",
"url": "http://the-internet.herokuapp.com/login",
"invalid_username": "invaliduser",
"invalid_password": "invalidpass"
}
}
pages/login_page.go
package pages
import (
"fmt"
"github.com/playwright-community/playwright-go"
)
// LoginPage 表示登录页面对象
type LoginPage struct {
page playwright.Page
loginURL string
}
// NewLoginPage 创建一个新的登录页面对象
func NewLoginPage(page playwright.Page) *LoginPage {
return &LoginPage{
page: page,
loginURL: "http://the-internet.herokuapp.com/login", // 默认URL,将被配置文件中的URL覆盖
}
}
// SetLoginURL 设置登录URL
func (l *LoginPage) SetLoginURL(url string) {
l.loginURL = url
}
// Navigate 导航到登录页面
func (l *LoginPage) Navigate() error {
_, err := l.page.Goto(l.loginURL, playwright.PageGotoOptions{
WaitUntil: playwright.WaitUntilStateNetworkidle,
})
return err
}
// Login 执行登录操作
func (l *LoginPage) Login(username, password string) error {
// 输入用户名
if err := l.page.Fill("#username", username); err != nil {
return fmt.Errorf("无法输入用户名: %w", err)
}
// 输入密码
if err := l.page.Fill("#password", password); err != nil {
return fmt.Errorf("无法输入密码: %w", err)
}
// 点击登录按钮
if err := l.page.Click("button[type=\"submit\"]"); err != nil {
return fmt.Errorf("无法点击登录按钮: %w", err)
}
// 等待页面加载完成
if err := l.page.WaitForLoadState(playwright.PageWaitForLoadStateOptions{
State: playwright.LoadStateNetworkidle,
}); err != nil {
return fmt.Errorf("等待页面加载超时: %w", err)
}
return nil
}
// VerifyLoginSuccess 验证登录是否成功
func (l *LoginPage) VerifyLoginSuccess() (bool, error) {
// 等待成功消息出现
successLocator := l.page.Locator(".flash.success")
if err := successLocator.WaitFor(playwright.LocatorWaitForOptions{
Timeout: playwright.Float(5000),
}); err != nil {
return false, fmt.Errorf("未找到成功消息: %w", err)
}
// 检查是否存在登出按钮
logoutButton, err := l.page.IsVisible("a[href=\"/logout\"]")
if err != nil {
return false, fmt.Errorf("检查登出按钮失败: %w", err)
}
return logoutButton, nil
}
// Logout 执行登出操作
func (l *LoginPage) Logout() error {
// 点击登出按钮
if err := l.page.Click("a[href=\"/logout\"]"); err != nil {
return fmt.Errorf("无法点击登出按钮: %w", err)
}
// 等待页面加载完成
if err := l.page.WaitForLoadState(playwright.PageWaitForLoadStateOptions{
State: playwright.LoadStateNetworkidle,
}); err != nil {
return fmt.Errorf("等待页面加载超时: %w", err)
}
return nil
}
// WaitForTimeout 等待指定时间
func (l *LoginPage) WaitForTimeout(ms int) {
l.page.WaitForTimeout(float64(ms))
}
// VerifyLoginFailed 验证登录失败场景
func (l *LoginPage) VerifyLoginFailed() (bool, error) {
// 等待错误消息出现
errorLocator := l.page.Locator(".flash.error")
if err := errorLocator.WaitFor(playwright.LocatorWaitForOptions{
Timeout: playwright.Float(5000),
}); err != nil {
return false, fmt.Errorf("未找到错误消息: %w", err)
}
// 检查是否仍在登录页面(通过登录按钮是否可见来判断)
loginButton, err := l.page.IsVisible("button[type=\"submit\"]")
if err != nil {
return false, fmt.Errorf("检查登录按钮失败: %w", err)
}
return loginButton, nil
}
main.go
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"time"
"github.com/playwright-community/playwright-go"
"github.com/wan/playwright-go-demo/config"
"github.com/wan/playwright-go-demo/pages"
"github.com/wan/playwright-go-demo/utils"
)
func main() {
// 清理旧的测试结果
if err := utils.CleanupOldTestResults(); err != nil {
log.Printf("警告: 清理旧测试结果失败: %v", err)
}
// 加载配置文件
configPath := "./config/config.json"
cfg, err := config.LoadConfig(configPath)
if err != nil {
log.Fatalf("加载配置文件失败: %v", err)
}
// 初始化Playwright
pw, err := playwright.Run()
if err != nil {
log.Fatalf("无法启动Playwright: %v", err)
}
defer pw.Stop()
// 确保截图目录存在
screenshotDir := "./screenshots"
if _, err := os.Stat(screenshotDir); os.IsNotExist(err) {
os.MkdirAll(screenshotDir, 0755)
}
// 确保视频目录存在
videoDir := "./videos"
if _, err := os.Stat(videoDir); os.IsNotExist(err) {
os.MkdirAll(videoDir, 0755)
}
// 遍历所有配置的浏览器,分别执行测试
for _, browserConfig := range cfg.Browsers {
// 为每个浏览器创建单独的测试报告
reportManager := utils.NewReportManager(fmt.Sprintf("%s浏览器登录测试", browserConfig.Type))
// 执行特定浏览器的测试
runTestWithBrowser(pw, browserConfig, cfg.Login, screenshotDir, videoDir, reportManager)
}
}
// runTestWithBrowser 使用特定浏览器执行测试
func runTestWithBrowser(pw *playwright.Playwright, browserConfig config.BrowserConfig, loginConfig config.LoginConfig, screenshotDir, videoDir string, reportManager *utils.ReportManager) {
// 根据配置选择浏览器类型
var browserType playwright.BrowserType
switch browserConfig.Type {
case "firefox":
browserType = pw.Firefox
case "webkit":
browserType = pw.WebKit
default:
browserType = pw.Chromium
}
fmt.Printf("开始使用 %s 浏览器执行测试\n", browserConfig.Type)
// 创建浏览器实例
browser, err := browserType.Launch(playwright.BrowserTypeLaunchOptions{
Headless: playwright.Bool(browserConfig.Headless),
SlowMo: playwright.Float(float64(browserConfig.SlowMo)),
})
if err != nil {
log.Printf("无法启动 %s 浏览器: %v", browserConfig.Type, err)
return
}
defer browser.Close()
// 创建上下文
contextOptions := playwright.BrowserNewContextOptions{
RecordVideo: &playwright.RecordVideo{
Dir: filepath.Join(videoDir, browserConfig.Type), // 为每个浏览器创建单独的视频目录
},
}
// 如果配置了最大化,设置视口大小为最大
if browserConfig.Maximized {
// 设置一个足够大的视口大小来模拟最大化
contextOptions.Viewport = &playwright.Size{
Width: 1920,
Height: 1080,
}
}
// 确保浏览器特定的视频目录存在
browserVideoDir := filepath.Join(videoDir, browserConfig.Type)
if _, err := os.Stat(browserVideoDir); os.IsNotExist(err) {
os.MkdirAll(browserVideoDir, 0755)
}
// 确保浏览器特定的截图目录存在
browserScreenshotDir := filepath.Join(screenshotDir, browserConfig.Type)
if _, err := os.Stat(browserScreenshotDir); os.IsNotExist(err) {
os.MkdirAll(browserScreenshotDir, 0755)
}
context, err := browser.NewContext(contextOptions)
if err != nil {
log.Printf("无法创建 %s 浏览器上下文: %v", browserConfig.Type, err)
return
}
defer context.Close()
// 创建页面
page, err := context.NewPage()
if err != nil {
log.Printf("无法创建 %s 浏览器页面: %v", browserConfig.Type, err)
return
}
reportManager.StartTest("登录测试")
// 执行测试
testStart := time.Now()
try := func() bool {
// 创建登录页面对象
loginPage := pages.NewLoginPage(page)
// 设置登录URL
loginPage.SetLoginURL(loginConfig.URL)
// 步骤1: 导航到登录页面
reportManager.StartStep("导航到登录页面")
if err := loginPage.Navigate(); err != nil {
// 失败时截图
screenshotPath := filepath.Join(browserScreenshotDir, "navigate_failure.png")
utils.TakeScreenshot(page, screenshotPath)
reportManager.EndStepFailure("导航到登录页面失败", err, screenshotPath)
return false
}
reportManager.EndStepSuccess("成功导航到登录页面")
// 测试场景1: 使用错误的用户名登录
reportManager.StartStep("测试错误用户名登录")
if err := loginPage.Login("wrong_username", loginConfig.Password); err != nil {
screenshotPath := filepath.Join(browserScreenshotDir, "wrong_username_input_failure.png")
utils.TakeScreenshot(page, screenshotPath)
reportManager.EndStepFailure("输入错误用户名失败", err, screenshotPath)
return false
}
if failed, err := loginPage.VerifyLoginFailed(); err != nil || !failed {
screenshotPath := filepath.Join(browserScreenshotDir, "wrong_username_verify_failure.png")
utils.TakeScreenshot(page, screenshotPath)
reportManager.EndStepFailure("验证错误用户名失败场景失败", err, screenshotPath)
return false
}
reportManager.EndStepSuccess("成功验证错误用户名登录失败场景")
// 测试场景2: 使用错误的密码登录
reportManager.StartStep("测试错误密码登录")
if err := loginPage.Login(loginConfig.Username, "wrong_password"); err != nil {
screenshotPath := filepath.Join(browserScreenshotDir, "wrong_password_input_failure.png")
utils.TakeScreenshot(page, screenshotPath)
reportManager.EndStepFailure("输入错误密码失败", err, screenshotPath)
return false
}
if failed, err := loginPage.VerifyLoginFailed(); err != nil || !failed {
screenshotPath := filepath.Join(browserScreenshotDir, "wrong_password_verify_failure.png")
utils.TakeScreenshot(page, screenshotPath)
reportManager.EndStepFailure("验证错误密码失败场景失败", err, screenshotPath)
return false
}
reportManager.EndStepSuccess("成功验证错误密码登录失败场景")
// 测试场景3: 使用正确的凭据登录
reportManager.StartStep("测试正确凭据登录")
if err := loginPage.Login(loginConfig.Username, loginConfig.Password); err != nil {
screenshotPath := filepath.Join(browserScreenshotDir, "login_failure.png")
utils.TakeScreenshot(page, screenshotPath)
reportManager.EndStepFailure("登录失败", err, screenshotPath)
return false
}
if success, err := loginPage.VerifyLoginSuccess(); err != nil || !success {
screenshotPath := filepath.Join(browserScreenshotDir, "verification_failure.png")
utils.TakeScreenshot(page, screenshotPath)
reportManager.EndStepFailure("验证登录失败", err, screenshotPath)
return false
}
reportManager.EndStepSuccess("成功验证正确凭据登录")
return true
}
success := try()
testDuration := time.Since(testStart)
// 完成测试报告
if success {
reportManager.LogSuccess("登录测试成功", testDuration)
} else {
reportManager.LogFailure("登录测试失败", testDuration)
}
// 生成测试报告
reportPath, err := reportManager.GenerateReport()
if err != nil {
log.Fatalf("生成测试报告失败: %v", err)
}
fmt.Printf("测试完成,报告已生成: %s\n", reportPath)
}
更多详细代码查看仓库:https://github.com/wan88888/playwright-go-demo
3.运行测试
本地运行
在终端执行命令:
go run main.go
测试报告