playwright-go实战:自动化登录测试

发布于:2025-03-24 ⋅ 阅读:(33) ⋅ 点赞:(0)

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

测试报告

远程测试