You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写依赖服务器时间的E2E测试?(Angular+Spring Boot+Playwright)

解决方案:基于Playwright的时间依赖型接口E2E测试实现

核心思路

要验证依赖后端时间的接口行为,关键是隔离后端的系统时间依赖,同时确保测试专属代码不会侵入生产环境。我们通过Spring的抽象时钟(Clock)替换硬编码的系统时间获取,配合仅测试环境生效的时间控制接口,再用Playwright完成端到端流程验证。


第一步:后端改造(Spring Boot)

1. 用Clock抽象替换直接时间获取

将业务代码中的硬编码时间判断改为依赖Clock,这样测试时可以注入自定义时钟:

@Service
public class OrderService {
    private final Clock clock;

    // 构造注入Clock,生产环境自动使用系统默认时钟
    public OrderService(Clock clock) {
        this.clock = clock;
    }

    public String cancelOrder(int id) {
        LocalTime now = LocalTime.now(clock);
        LocalTime twoPM = LocalTime.of(14, 0);
        LocalTime threePM = LocalTime.of(15, 0);
        
        if (now.isAfter(twoPM) && now.isBefore(threePM)) {
            return "Canceled";
        } else {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot cancel order outside 2-3 PM");
        }
    }
}

2. 环境隔离的时钟配置

  • 生产环境配置:默认使用系统时钟,仅在非test环境生效
@Configuration
@Profile("!test")
public class ProductionClockConfig {
    @Bean
    public Clock systemClock() {
        return Clock.systemDefaultZone();
    }
}
  • 测试环境配置:提供可修改的可变时钟,仅在test环境生效
@Configuration
@Profile("test")
public class TestClockConfig {
    @Bean
    public MutableClock mutableClock() {
        return new MutableClock(Clock.systemDefaultZone());
    }

    // 自定义可变时钟类,支持动态设置时间
    public static class MutableClock extends Clock {
        private Clock delegate;

        public MutableClock(Clock delegate) {
            this.delegate = delegate;
        }

        public void setClock(Clock clock) {
            this.delegate = clock;
        }

        @Override
        public ZoneId getZone() {
            return delegate.getZone();
        }

        @Override
        public Clock withZone(ZoneId zone) {
            return delegate.withZone(zone);
        }

        @Override
        public Instant instant() {
            return delegate.instant();
        }
    }
}

3. 测试专属时间控制接口

添加仅在test环境暴露的接口,用于Playwright测试时修改后端时间:

@RestController
@Profile("test")
@RequestMapping("/test/clock")
public class TestClockController {
    private final TestClockConfig.MutableClock mutableClock;

    public TestClockController(TestClockConfig.MutableClock mutableClock) {
        this.mutableClock = mutableClock;
    }

    @PostMapping("/set-time")
    public void setTime(@RequestParam LocalTime time) {
        // 设置为当天的指定时间
        LocalDate today = LocalDate.now();
        Instant targetInstant = LocalDateTime.of(today, time)
                .atZone(ZoneId.systemDefault())
                .toInstant();
        mutableClock.setClock(Clock.fixed(targetInstant, ZoneId.systemDefault()));
    }

    @PostMapping("/reset")
    public void resetClock() {
        mutableClock.setClock(Clock.systemDefaultZone());
    }
}

第二步:Playwright E2E测试编写(TypeScript示例)

通过调用测试接口设置后端时间,再执行前端操作验证结果:

import { test, expect } from '@playwright/test';

test.describe('Order Cancel Functionality', () => {
    // 每个测试前后重置后端时钟,避免测试污染
    test.beforeEach(async ({ request }) => {
        await request.post('/test/clock/reset');
    });

    test.afterEach(async ({ request }) => {
        await request.post('/test/clock/reset');
    });

    test('shouldCancelOrder', async ({ page, request }) => {
        // 设置后端时间为14:30(允许取消的时间段)
        await request.post('/test/clock/set-time', {
            params: { time: '14:30:00' }
        });

        // 前端操作流程:打开订单页 → 点击取消按钮
        await page.goto('/orders');
        await page.getByRole('button', { name: 'Cancel Order' }).click();

        // 验证取消成功提示
        await expect(page.getByText('Canceled')).toBeVisible();
    });

    test('shouldNotCancelOrder', async ({ page, request }) => {
        // 设置后端时间为15:30(不允许取消的时间段)
        await request.post('/test/clock/set-time', {
            params: { time: '15:30:00' }
        });

        // 前端操作流程:打开订单页 → 点击取消按钮
        await page.goto('/orders');
        await page.getByRole('button', { name: 'Cancel Order' }).click();

        // 验证错误提示
        await expect(page.getByText('Cannot cancel order outside 2-3 PM')).toBeVisible();
    });
});

确保测试代码不进入生产环境的关键措施

  1. Spring Profile隔离:所有测试专属类(TestClockConfig、TestClockController)都标记@Profile("test"),生产环境不会加载这些类。
  2. 构建阶段排除:Maven/Gradle的生产打包配置中,排除test相关的代码和依赖。
  3. 网关/安全拦截:生产环境的API网关或Spring Security配置,直接拦截所有/test/前缀的请求,双重保险。

内容的提问来源于stack exchange,提问作者Tristate

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 17:01:09