《基于Selenium的网页聊天室自动化测试实战报告》

发布于:2025-03-04 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、项目背景与技术选型

  1. 项目简介

    • 目标系统:基于WebSocket的实时聊天室

    • 核心功能:用户注册/登录、会话框发送信息、好友列表、信息发送

    • 技术栈:html + Springboot + MySQL数据库

  2. 为什么选择Selenium

    • 支持多浏览器兼容性测试(Chrome/Firefox/Edge)

    • 完善的元素定位体系(XPath/CSS Selector)

    • 动态内容处理能力(WebDriverWait机制)

    • 跨平台执行能力(Windows/Linux/macOS)

  3. 测试环境

    在开始测试之前,我们需要准备好测试环境。以下是测试所需的硬件和软件环境:

  4. 硬件:普通个人电脑

  5. 操作系统:Windows 10 或 Windows 11

  6. 浏览器:Chrome(版本 128.0.6613.84 或以上)

  7. 项目运行环境:maven、JDK1.8

  8. 自动化测试工具:Selenium + JUnit5

二、测试用例设计+手动测试

1. 用户注册

用户注册是聊天室的第一步,我们需要确保用户能够成功注册账号。以下是注册功能的测试用例:

  • 测试场景1:输入正确的用户名和密码,点击注册按钮。

    • 预期结果:注册成功,弹出提示框“注册成功!”。

  • 测试场景2:输入已存在的用户名和密码,点击注册按钮。

    • 预期结果:注册失败,弹出提示框“注册失败!”。

  • 测试场景3:用户名或密码为空,点击注册按钮。

    • 预期结果:注册失败,弹出提示框“您输入的用户名或密码为空!”。

2. 用户登录

用户登录是进入聊天室的关键步骤,以下是登录功能的测试用例:

  • 测试场景1:输入正确的用户名和密码,点击登录按钮。

    • 预期结果:弹出提示框“登录成功”,登录成功,跳转到会话页面。

  • 测试场景2:输入正确的用户名,错误的密码,点击登录按钮。

    • 预期结果:登录失败,弹出提示框“登录失败!”。

  • 测试场景3:输入未注册的用户名和密码,点击登录按钮。

    • 预期结果:登录失败,弹出提示框“登录失败!”。

  • 测试场景4:用户名或密码为空,点击登录按钮。

    • 预期结果:登录失败,弹出提示框“您输入的用户名或密码为空!”。

3. 会话功能

会话功能是聊天室的核心,以下是会话功能的测试用例:

  • 测试场景1:未登录状态下,打开会话页面。

    • 预期结果:弹出提示框“当前用户未登录!”。

  • 测试场景2:登录状态下,点击会话按钮。

    • 预期结果:会话按钮变绿,跳转会话列表页面。

  • 测试场景3:点击具体会话,查看消息。

    • 预期结果:消息页面显示当前会话的用户名和消息内容。

4. 消息发送和接收

消息发送和接收是聊天室的核心功能,以下是相关测试用例:

  • 测试场景1:在输入框中输入消息,点击发送按钮。

    • 预期结果:消息发送成功,输入框清空,消息页面显示发送的内容。

  • 测试场景2:发送消息后,接收方查看消息。

    • 预期结果:接收方能够看到发送方的消息。

等等场景

三、自动化测试实现

1.pom.xml文件引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>ChatroomAutoTest</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>5.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency><!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>5.9.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>5.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <version>1.9.1</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-suite -->
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <version>1.9.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.9.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>
  1. 引入自动化测试工具

    • selenium-java 用于编写和执行自动化测试脚本。

    • webdrivermanager 用于自动管理 WebDriver 驱动程序,避免手动下载和配置。

  2. 引入文件操作工具

    • commons-io 提供文件和 IO 操作的工具类,简化文件处理逻辑。

  3. 单元测试框架:通过 JUnit 5,可以编写和运行单元测试,验证网页聊天室的各项功能是否正常工作。

  4. 参数化测试:通过 JUnit Jupiter Params,可以为测试方法提供不同的参数,减少重复代码。

  5. 测试套件管理:通过 JUnit Platform Suite,可以将多个测试类或测试模块组合在一起,一次性运行。

2.写下AutoTestUtils类

方便在每个测试用例中来

  • 获取浏览器驱动
  • 获取屏幕截图,将用例执行结果保存下来
  • 获取当前时间(记录每个屏幕截图是什么时候拍摄的)
package common;

import io.github.bonigarcia.wdm.WebDriverManager;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Date;

public class Utils {
    public static WebDriver driver;

    public static WebDriver createDriver(){
        if (driver == null){
            WebDriverManager.chromedriver().setup();
            ChromeOptions options = new ChromeOptions();
            //允许访问所有链接
            options.addArguments("--remote-allow-origins=*");

            driver = new ChromeDriver(options);

            //等待
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));

        }
        return driver;
    }
    public Utils(String url){
        //调用driver对象
        driver = createDriver();
        //访问url
        driver.get(url);
    }
    public void getScreenShot(String str) throws IOException {
        //      ./src/test/image/
        //                      /2025-02-15/
        //                                  /test01-174530.png
        //                                  /test02-174530.png
        //                      /2025-02-16/
        //                                  /test01-174530.png
        //                                  /test02-174530.png
        //屏幕截图
        SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sim2 = new SimpleDateFormat("HHmmssSS");

        String dirTime = sim1.format(System.currentTimeMillis());
        String fileTime = sim2.format(System.currentTimeMillis());

        //.src/test/image/2025-02-15/test-174530.png
        String filename = "./src/test/image" + dirTime + "/" + str + "-" + fileTime + ".png";
        System.out.println("filename:" + filename);
        File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        //srcFile放到指定位置
        FileUtils.copyFile(srcFile, new File(filename));
    }
}


3.针对各种功能做自动化测试

        3.1注册页面功能测试

RegisterPageTest.java

package Test;

import common.Utils;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.io.IOException;
import java.time.Duration;

import static java.lang.Thread.sleep;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RegisterPageTest extends Utils {
    //获取到驱动
    private static WebDriver driver = Utils.getDriver();

    /**
     * 打开网页
     * 注册网页一般是从登录页面进入
     */
    @BeforeAll
    public static void openWeb() {
        //打开登录页面
        driver.get("http://127.0.0.1:8080/register.html");
        //再点击登录页面的注册链接按钮
/*
        driver.findElement(By.cssSelector("body > div.login-container > div > div:nth-child(4) > a")).click();
*/
        driver.manage().window().maximize();
    }

    /**
     * 验证网页正常打开
     */
    @Test
    @Order(1)
    public void elementsAppear() throws IOException {
        //用户名输入框
        driver.findElement(By.cssSelector("#username"));
        //密码输入框
        driver.findElement(By.cssSelector("#password"));
        //注册按钮
        driver.findElement(By.cssSelector("#submit"));
        //登录账号链接
/*
        driver.findElement(By.cssSelector("body > div.register-container > div > div:nth-child(4) > a"));
*/
        getScreenShot(getClass().getName());
    }

    /**
     * 注册失败测试用例
     * 参数: 尝试注册已注册用户
     */

    @ParameterizedTest
    @CsvSource({"zhangsan,123"})
    @Order(2)
    public void regFunTest(String username, String password) throws IOException {
        // 输入测试数据
        driver.findElement(By.cssSelector("#username")).sendKeys(username);
        driver.findElement(By.cssSelector("#password")).sendKeys(password);
        driver.findElement(By.cssSelector("#submit")).click();

        // 显式等待弹窗出现(最多等待10秒)
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        try {
            Alert alert = wait.until(ExpectedConditions.alertIsPresent());
            String actual = alert.getText();
            alert.accept(); // 关闭弹窗

            Assertions.assertEquals("注册失败!", actual);
        } catch (TimeoutException e) {
            // 如果弹窗未出现,直接断言失败
            Assertions.fail("未出现预期弹窗");
        }

        getScreenShot(getClass().getName());
    }

    @ParameterizedTest
    @CsvSource({"zmjjk,123,注册成功!"})
    @Order(3)
    public void regTest(String username, String password, String expect) throws IOException {
        // 输入测试数据
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();
        driver.findElement(By.cssSelector("#username")).sendKeys(username);
        driver.findElement(By.cssSelector("#password")).sendKeys(password);
        driver.findElement(By.cssSelector("#submit")).click();

        // 显式等待弹窗
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        try {
            Alert alert = wait.until(ExpectedConditions.alertIsPresent());
            String actual = alert.getText();
            alert.accept(); // 关闭弹窗

            Assertions.assertEquals(expect, actual);
        } catch (TimeoutException e) {
            Assertions.fail("未出现预期弹窗");
        }

        getScreenShot(getClass().getName());
    }}

        3.2登录页面功能测试

LoginPageTest.java

package Test;



import common.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.io.IOException;

import static java.lang.Thread.sleep;

public class LoginPageTest extends Utils {

    //获取到驱动
    private static WebDriver driver = Utils.getDriver();

    /**
     * 打开页面
     * 注册页面一般是从登录页面进入
     */
    @BeforeAll
    public static void openWeb() {
        //先打开登录页面
        driver.get("http://127.0.0.1:8080/login.html");
        driver.manage().window().maximize();
    }

    /**
     * 检测登录页面是否能正常打开
     */
    @Test
    @Order(1)
    public void elementsAppear() throws IOException {
        //用户名输入框
        driver.findElement(By.cssSelector("#username"));
        //密码输入框
        driver.findElement(By.cssSelector("#password"));
        //注册账号链接
/*
        driver.findElement(By.cssSelector("body > div.login-container > div > div:nth-child(4) > a"));
*/
        //登录按钮
        driver.findElement(By.cssSelector("#submit"));
        //屏幕截图
        getScreenShot(getClass().getName());
    }

    /**
     * 异常测试登录功能
     * 参数一:尝试登录未注册用户
     * 参数二:密码输入错误,登录账号
     */
    @ParameterizedTest
    @CsvSource({"zhang,123","zhangsan,123456"})
    @Order(2)
    public void loginAbnormalTest(String username,String password) throws InterruptedException, IOException {
        //拿到元素
        WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
        WebElement inputPassword = driver.findElement(By.cssSelector("#password"));
        WebElement button = driver.findElement(By.cssSelector("#submit"));
        //清除用户名,密码
        inputUserName.clear();
        inputPassword.clear();
        //输入账号,密码
        inputUserName.sendKeys(username);
        inputPassword.sendKeys(password);
        //点击登录
        button.click();
        //等待弹窗
        sleep(3000);
        //期望结果
        String expect = "登录失败!";
        String actual = "登录失败!";
        Alert alert = driver.switchTo().alert();
        if(alert != null) {
            alert.accept();
        } else {
            actual = "当前用例执行失败!";
        }
        Assertions.assertEquals(expect,actual);
        //屏幕截图
        getScreenShot(getClass().getName());
    }

    /**
     * 正常登录测试
     */
    @ParameterizedTest
    @CsvSource({"'zhangsan','123','登录成功!'"})
    @Order(3)
    public void loginNormalTest(String userName,String password,String expect) throws InterruptedException, IOException {
        //发现元素
        WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
        WebElement inputPassword = driver.findElement(By.cssSelector("#password"));
        WebElement button = driver.findElement(By.cssSelector("#submit"));
        //清除用户名密码
        inputUserName.clear();
        inputPassword.clear();
        //输入用户名,密码
        inputUserName.sendKeys(userName);
        inputPassword.sendKeys(password);
        //点击登录
        button.click();
        //等待弹窗
        sleep(3000);
        //期望结果
        String actual = driver.switchTo().alert().getText();
        //断言
        Assertions.assertEquals(expect,actual);
        //处理弹窗
        driver.switchTo().alert().accept();
        //屏幕截图
        getScreenShot(getClass().getName());

    }
}

        3.3不登录页面功能测试

NoLoginTest.java
package Test;


import common.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.Alert;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

import java.io.IOException;

import static java.lang.Thread.sleep;


public class NoLoginTest extends Utils {
    //获取驱动
    private static WebDriver driver = Utils.getDriver();

    @Test
    public void Intercept() throws InterruptedException, IOException {
        //在未登录的情况下,跳转会话页
        driver.get("http://127.0.0.1:8080/client.html");
        sleep(2000);
        //期望结果
        String expect = "当前用户未登录!";
        String actual = "当前用户未登录!";
        Alert alert = driver.switchTo().alert();
        if (alert != null) {
            alert.accept();
        } else {
            actual = "当前用例执行失败";
        }
        //屏幕截图
        getScreenShot(getClass().getName());
        //断言
        Assertions.assertEquals(expect,actual);
    }
}

 3.4客户端页面功能测试

ClientPageTest.java

package Test;


import common.Utils;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.io.IOException;
import java.time.Duration;

import static java.lang.Thread.sleep;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ClientPageTest extends Utils {
    //获得驱动
    public static WebDriver driver = Utils.getDriver();

    /**
     * 打开会话页面
     */
    @BeforeAll
    public static void openWeb() {
        //先打开会话页面
        driver.get("http://127.0.0.1:8080/client.html");
        driver.manage().window().maximize();
    }

    /**
     * 测试会话页面正常打开
     */
    @Test
    @Order(1)
    public void elementsAppear() throws IOException {
        //主页个人账号名
        driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.user"));
        //搜索输入框
        driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.search > input[type=text]"));
        //搜索按钮
        driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.search > button"));
        //会话标签图
        driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.tab > div.tab-session"));
        //好友标签图
        driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.tab > div.tab-friend"));
        //聊天输入框
        driver.findElement(By.cssSelector("body > div.client-container > div > div.right > textarea"));
        //发送按钮
        driver.findElement(By.cssSelector("body > div.client-container > div > div.right > div.ctrl > button"));
        //屏幕截图
        getScreenShot(getClass().getName());
    }


    /**
     * 点击具体会话
     * 检验:消息页面展示当前会话用户名字
     */

    @Test
    @Order(2)
    void SessionList() throws InterruptedException {
        Thread.sleep(3000);
        // 点击第一个会话项
        driver.findElement(By.xpath("//*[@id=\"session-list\"]/li[1]/h3")).click();
        Thread.sleep(3000);

        // 显式等待标题元素加载并可见
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        WebElement titleElement = wait.until(ExpectedConditions.visibilityOfElementLocated(
                By.cssSelector("body > div.client-container > div > div.right > div.title")
        ));

        // 获取并处理文本
        String actual = titleElement.getText().trim();
        System.out.println("Actual title text: '" + actual + "'");  // 调试输出

        // 断言
        Assertions.assertEquals("lisi", actual);
    }

    /**
     * 点击好友列表 (显式等待版本)
     */
    @Order(3)
    @Test
    void FriendList() {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

        // 1. 点击好友标签
        WebElement friendTab = wait.until(ExpectedConditions.elementToBeClickable(
                By.cssSelector("div.tab-friend")
        ));
        friendTab.click();

        // 2. 等待好友列表加载
        wait.until(ExpectedConditions.visibilityOfElementLocated(
                By.id("friend-list")
        ));

        // 3. 点击第一个好友
        WebElement firstFriend = wait.until(ExpectedConditions.elementToBeClickable(
                By.cssSelector("#friend-list li:first-child h4")
        ));
        firstFriend.click();

        // 4. 验证会话列表置顶项
        WebElement selectedSession = wait.until(ExpectedConditions.visibilityOfElementLocated(
                By.cssSelector("#session-list li.selected h3")
        ));
        Assertions.assertEquals("lisi", selectedSession.getText());
    }

    /**
     * 测试聊天发送功能 (显式等待版本)
     */
    @ParameterizedTest
    @CsvSource({"正在自动化测试中...", "测试发送成功", "测试结束!"})
    @Order(4)
    public void TalkSendTest(String msg) throws IOException {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

        // 1. 点击第二个会话
        WebElement secondSession = wait.until(ExpectedConditions.elementToBeClickable(
                By.cssSelector("#session-list li:nth-child(2) h3")
        ));
        secondSession.click();

        // 2. 等待输入框就绪
        WebElement inputBox = wait.until(ExpectedConditions.elementToBeClickable(
                By.cssSelector("textarea")
        ));

        // 3. 输入并发送消息
        inputBox.sendKeys(msg);
        driver.findElement(By.cssSelector("button")).click();

        // 4. 验证消息发送成功(根据实际DOM结构调整选择器)
        wait.until(ExpectedConditions.textToBePresentInElementLocated(
                By.cssSelector(".message-show div:last-child p"),
                msg
        ));

        getScreenShot(getClass().getName());
    }

    /**
     * 测试聊天接收功能 (显式等待版本)
     */
    @Test
    @Order(5)
    public void TalkAcceptTest() {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));

        // 1. 跳转登录页
        driver.get("http://127.0.0.1:8080/login.html");

        // 2. 填写登录表单
        wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("username")))
                .sendKeys("lisi");
        driver.findElement(By.id("password")).sendKeys("123");
        driver.findElement(By.id("submit")).click();

        // 3. 处理登录弹窗
        Alert alert = wait.until(ExpectedConditions.alertIsPresent());
        alert.accept();

        // 4. 等待主界面加载
        wait.until(ExpectedConditions.titleContains("Client Page"));

        // 5. 点击第一个会话
        WebElement firstSession = wait.until(ExpectedConditions.elementToBeClickable(
                By.cssSelector("#session-list li:first-child h3")
        ));
        firstSession.click();

        // 6. 验证会话对象
        WebElement chatTitle = wait.until(ExpectedConditions.visibilityOfElementLocated(
                By.cssSelector("div.title")
        ));
        Assertions.assertEquals("zhangsan", chatTitle.getText());

        // 7. 获取最新消息(动态定位最后一条)
        WebElement lastMessage = wait.until(ExpectedConditions.presenceOfElementLocated(
                By.cssSelector(".message-show div:last-child p")
        ));
        Assertions.assertEquals("测试结束!", lastMessage.getText());
    }
}

  3.5退出驱动测试

DriverQuitTest.java

package common;


import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class DriverQuitTest extends Utils {
    private static WebDriver driver = Utils.getDriver();

    /**
     * 退出驱动
     */
    @Test
    public void quitWeb() {
        driver.quit();
    }
}

3.6Junit套件使用 

package Test;

import Test.ClientPageTest;
import Test.LoginPageTest;
import Test.NoLoginTest;
import Test.RegisterPageTest;
import common.DriverQuitTest;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectClasses({RegisterPageTest.class, NoLoginTest.class, LoginPageTest.class, ClientPageTest.class, DriverQuitTest.class})
public class RunSuite {
    public RunSuite() {

    }

}



整体包、类、方法设计如下

有部分问题待解决,上述会话框问题和接收消息问题代码经过修改已经解决


网站公告

今日签到

点亮在社区的每一天
去签到