obtain a working Docker image with Windows inside

如何有效的在docker 里面run selenium
https://blog.aerokube.com/selenium-on-windows-docker-revolution-f5a7eab205ad

start container 之前需要设置好docker proxy:
https://docs.docker.com/config/daemon/systemd/

  1. Create a systemd drop-in directory for the docker service:

    $ sudo mkdir -p /etc/systemd/system/docker.service.d
    
    
  2. Create a file named /etc/systemd/system/docker.service.d/http-proxy.conf that adds the HTTP_PROXY environment variable:

    [Service]
    Environment="HTTP_PROXY=http://proxy.example.com:80"
    
    

    If you are behind an HTTPS proxy server, set the HTTPS_PROXY environment variable:

    [Service]
    Environment="HTTPS_PROXY=https://proxy.example.com:443"
    
    

    Multiple environment variables can be set; to set both a non-HTTPS and a HTTPs proxy;

    [Service]
    Environment="HTTP_PROXY=http://proxy.example.com:80"
    Environment="HTTPS_PROXY=https://proxy.example.com:443"
    
    
  3. If you have internal Docker registries that you need to contact without proxying you can specify them via the NO_PROXY environment variable.

    The NO_PROXY variable specifies a string that contains comma-separated values for hosts that should be excluded from proxying. These are the options you can specify to exclude hosts:

    • IP address prefix (1.2.3.4)
    • Domain name, or a special DNS label (*)
    • A domain name matches that name and all subdomains. A domain name with a leading “.” matches subdomains only. For example, given the domains foo.example.com and example.com:
      • example.com matches example.com and foo.example.com, and
      • .example.com matches only foo.example.com
    • A single asterisk (*) indicates that no proxying should be done
    • Literal port numbers are accepted by IP address prefixes (1.2.3.4:80) and domain names (foo.example.com:80)

    Config example:

    [Service]
    Environment="HTTP_PROXY=http://proxy.example.com:80"
    Environment="HTTPS_PROXY=https://proxy.example.com:443"
    Environment="NO_PROXY=localhost,127.0.0.1,docker-registry.example.com,.corp"
    
    
  4. Flush changes and restart Docker

    $ sudo systemctl daemon-reload
    $ sudo systemctl restart docker
    
    
  5. Verify that the configuration has been loaded and matches the changes you made, for example:

    $ sudo systemctl show --property=Environment docker
    
    Environment=HTTP_PROXY=http://proxy.example.com:80 HTTPS_PROXY=https://proxy.example.com:443 NO_PROXY=localhost,127.0.0.1,docker-registry.example.com,.corp
    
    
start the selenoid container:

docker run -d                                 \
--name selenoid                                 \
-p 4444:4444                                    \
-v /var/run/docker.sock:/var/run/docker.sock    \
-v $PWD:/etc/selenoid/:ro              \
-v /opt/selenoid/video/:/opt/selenoid/video/            \
-v /opt/selenoid/logs/:/opt/selenoid/logs/    \
-v /home/admin/selenium/files/Certificate:/home/admin/selenium/files/Certificate           \
-e OVERRIDE_VIDEO_OUTPUT_DIR=/opt/selenoid/video/       \
aerokube/selenoid:latest-release  -limit 300 -log-output-dir /opt/selenoid/logs -save-all-logs \
-video-output-dir /opt/selenoid/video/ -mem 1g -cpu 1.0 


start the selenoid-ui container:
docker run -d   --name selenoid-ui  --link selenoid  -p 8080:8080   aerokube/selenoid-ui --selenoid-uri=http://172.17.0.1:4444

可用的cmd:

docker run -d                                 \
--name selenoid                                 \
-p 4444:4444                                    \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock    \
-v $PWD:/etc/selenoid/:ro              \
-v /opt/selenoid/video/:/opt/selenoid/video/            \
-v /opt/selenoid/logs/:/opt/selenoid/logs/    \
-v /home/admin/selenium/files/Certificate:/home/admin/selenium/files/Certificate           \
-e OVERRIDE_VIDEO_OUTPUT_DIR=/opt/selenoid/video/       \
aerokube/selenoid:latest-release -limit 300 -timeout 45m -log-output-dir /opt/selenoid/logs -save-all-logs \
-video-output-dir /opt/selenoid/video/


参考:docker run -d --name selenoid -p 4444:4444 -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd)/config/:/etc/selenoid/:ro aerokube/selenoid:latest-release -timeout 3m0s -conf /etc/selenoid/browsers.json

 
 
docker run -d   --name selenoid-ui  --link selenoid  -p 8080:8080  --restart=always  aerokube/selenoid-ui --selenoid-uri=http://172.17.0.1:4444

第二种方法 -- 二进制文件的方法:

wget https://github.com/aerokube/cm/releases/download/1.8.1/cm_linux_amd64
mv cm_linux_amd64 cm
chmod +x cm
http_proxy=http://web-proxy.us.softwaregrp.net:8080
HTTP_PROXY=http://web-proxy.us.softwaregrp.net:8080/ ./cm selenoid start --vnc --tmpfs 128
HTTP_PROXY=http://web-proxy.us.softwaregrp.net:8080/ ./cm selenoid-ui start

删除docker container的方法:

docker kill $(docker ps -a | grep sele)
docker rm $(docker ps -a | grep sele)
docker ps -a | grep sele 

docker kill $(docker ps -a -q)
docker rm $(docker ps -a -q)
docker ps -a

https://aerokube.com/moon-cloud/

docker run 加proxy:
https://blog.csdn.net/chang_harry/article/details/53464778

youtube:
https://www.youtube.com/watch?v=RJCvvfSE2FE

selenoid-ui not loading running sessions with error:
是因为docker代理问题导致的
在docker pull到所需的image之后要删除docker proxy
cmd


i. Enable the service, run: systemctl enable docker.service
ii. Run: mkdir -p /usr/lib/systemd/system/docker.service.d
iii. Run:cat  /usr/lib/systemd/system/docker.service.d/http_proxy.conf
             vi  /usr/lib/systemd/system/docker.service.d/http_proxy.conf

Environment="HTTP_PROXY=http://web-proxy.us.softwaregrp.net:8080" "https_proxy=http://web-proxy.us.softwaregrp.net:8080"
变成:
空
iv. Reload the configuration:

systemctl daemon-reload
service docker restart


v. Restart docker:
service docker restart
vi. Check if you can pull images from Docker Hub:
docker pull hello-world

couldn't it be because of some proxies configured on your host?

然后按照这个文档一步步进行下去:
http://aerokube.com/selenoid-ui/latest/#_quick_start_guide

selenoid 属性设置:
https://github.com/aerokube/selenoid/blob/master/docs/special-capabilities.adoc

selenoid example:
https://www.pawangaria.com/post/docker/selenoid-as-alternative-selenium-grid-with-docker/
https://medium.com/javarevisited/selenide-in-testing-process-automatisation-through-selenoid-in-the-docker-container-48e659d2ee72

Since now we have the Configuration.browser = “chrome” property, we delete Property baseurl which defined the browser for running our tests:

@BeforeClass 
public static void setUp() {
       Configuration.remote = "http://10.0.75.1:4444/wd/hub";               
       Configuration.browser = "chrome";          
       Configuration.browserSize = "1920x1080";      
       DesiredCapabilities capabilities = new DesiredCapabilities();     
       capabilities.setCapability(capabilityName: "enableVNC", value: true);        
       capabilities.setCapability(capabilityName: "enableVideo", value: true);      
       Configuration.browserCapabilities = capabilities;

Selenoid advanced capabilities
Data storage in RAM: Selenoid stores all temporary files in Tmpfs. It is a temporary file repository that allows storing files in RAM. As we know, access to RAM is performed much faster than to the file system of the hard drive.
Various screen resolution types: we can configure the appropriate screen resolution for a running container on its own by setting the required parameters in the Browser Capabilities.
Video recording of tests: it’s possible to record the video of the tests performed. For instance, the activation in the Google Chrome browser is implemented by setting the parameter true in the Browser Capabilities:

ChromeOptions options = new ChromeOptions ();
options.setCapability (“enableVideo”, true);

seleniod 设置代理和其他参数:
https://github.com/aerokube/cm/blob/master/docs/selenoid-commands.adoc

selenoid 参数设置:
https://www.swtestacademy.com/selenoid-tutorial/

docker run -d --name selenoid                     \
-p 4444:4444                                    \
-v ~/.aerokube/selenoid/:/etc/selenoid/:ro      \
-v /var/run/docker.sock:/var/run/docker.sock    \
aerokube/selenoid:latest-release                \
-conf /etc/selenoid/browsers.json -limit 10 \
-video-output-dir /opt/selenoid/video/ -mem 1g -cpu 1.0

Note: If you are using selenoid binary instead of the docker image, you should pass arguments as :

./selenoid -conf /etc/selenoid/browsers.json \
-limit 10 -video-output-dir /opt/selenoid/video/ \
-mem 1g -cpu 1.0

开始跑java代码的时候设置capbilitity 属性代码:
https://www.lambdatest.com/blog/desired-capabilities-in-selenium-testing/
This will download VNC included images and start the selenoid. Don’t forget to set enableVNC capability in your test, which in my case:

package com.lambdatest;


//TestNG Todo : Sample App
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import org.testng.asserts.Assertion;

import com.beust.jcommander.Parameter;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Set;
import java.util.concurrent.TimeUnit;
 
public class Test2{
    
    public String username = "Your_LambdaTest_Username";
    public String accesskey = "Your_LambdaTest_Access_Key";
    public static RemoteWebDriver driver = null;
    public String gridURL = "@hub.lambdatest.com/wd/hub";
    boolean status = false;
  
    
    //Setting up capabilities to run our test script
    @Parameters(value= {"browser","version"})
    @BeforeClass
    public void setUp(String browser, String version) throws Exception {
        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setCapability("browserName", browser);
        capabilities.setCapability("version", version);
        capabilities.setCapability("platform", "win10"); // If this cap isn't specified, it will just get the any available one
        capabilities.setCapability("build", "LambdaTestSampleApp");
        capabilities.setCapability("name", "LambdaTestJavaSample");
        capabilities.setCapability("network", true); // To enable network logs
        capabilities.setCapability("visual", true); // To enable step by step screenshot
        capabilities.setCapability("video", true); // To enable video recording
        capabilities.setCapability("console", true); // To capture console logs

         capabilities.setCapability("selenium_version","4.0.0-alpha-2");
         capabilities.setCapability("timezone","UTC+05:30");
         capabilities.setCapability("geoLocation","IN");
         capabilities.setCapability("chrome.driver","78.0");


        try {
            driver = new RemoteWebDriver(new URL("https://" + username + ":" + accesskey + gridURL), capabilities);
        } catch (MalformedURLException e) {
            System.out.println("Invalid grid URL");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
               
    }
    
    //Opening browser with the given URL and navigate to Registration Page
    @BeforeMethod
    public void openBrowser()
    {
 //     driver.manage().deleteAllCookies();
        
        driver.get("https://www.lambdatest.com/");
        
        driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS);
        driver.manage().timeouts().pageLoadTimeout(15, TimeUnit.SECONDS);
        
        WebElement signUpButton = driver.findElement(By.xpath("//a[contains(text(),'Start Free Testing')]"));
        signUpButton.click(); 
        
    }
    
    //Verifying elements on Registration page
    @Test
    public void verifyElementsOnPageTest()
    {
        WebElement lambdaTestLogo = driver.findElement(By.xpath("//p[@class='signup-titel']"));
        lambdaTestLogo.isDisplayed();
        
        WebElement signUpTitle = driver.findElement(By.xpath("//p[@class='signup-titel']"));
        signUpTitle.isDisplayed();
        
        WebElement termsText = driver.findElement(By.xpath("//label[@class='woo']"));
        termsText.isDisplayed();
        
        WebElement loginLinkText = driver.findElement(By.xpath("//p[@class='login-in-link test-left']"));
        loginLinkText.isDisplayed();
        
    }  
    
    // Closing the browser session after completing each test case
    @AfterClass
    public void tearDown() throws Exception {
       if (driver != null) {
            ((JavascriptExecutor) driver).executeScript("lambda-status=" + status);
            driver.quit();
        }
    }
}

https://www.tabnine.com/code/java/methods/org.openqa.selenium.remote.RemoteWebDriver/%3Cinit%3E

设置video 的属性:

https://www.swtestacademy.com/selenoid-tutorial/

https://www.pawangaria.com/post/docker/selenoid-as-alternative-selenium-grid-with-docker/

https://testerhome.com/topics/27203

https://medium.com/javarevisited/selenide-in-testing-process-automatisation-through-selenoid-in-the-docker-container-48e659d2ee72

增加录制视频属性:
https://github.com/aerokube/selenoid/blob/master/docs/video.adoc
增加保存logs属性:
https://github.com/aerokube/selenoid/blob/68ce8aa754e473fe4c961178e5974edd337e625c/docs/logs.adoc

import time
from selenium import webdriver

class TestClass:

    def test_baidu_chrome(self):
        capabilities = {
            # 测试用例名称
            "name": "test_baidu_chrome",
            # 浏览器类型
            "browserName": "chrome",
            # 版本号
            "version": "85.0",
            # 开启远程VNC实时画面
            "enableVNC": True,
            # 开启视频录制
            "enableVideo": True,
            # 保存视频名称
            "videoName": "test_baidu_chrome.mp4"
        }
        driver = webdriver.Remote(
            command_executor="http://10.118.80.65:4444/wd/hub",
            desired_capabilities=capabilities)

        driver.get('https://www.baidu.com')
        driver.find_element_by_id('kw').send_keys('selenoid')
        time.sleep(2)

    def test_baidu_firefox(self):
        capabilities = {
            # 测试用例名称
            "name": "test_baidu_firefox",
            # 浏览器类型
            "browserName": "firefox",
            # 版本号
            "version": "79.0",
            # 开启远程VNC实时画面
            "enableVNC": True,
            # 开启视频录制
            "enableVideo": True,
            # 保存视频名称
            "videoName": "test_baidu_firefox.mp4"
        }
        driver = webdriver.Remote(
            command_executor="http://10.118.80.65:4444/wd/hub",
            desired_capabilities=capabilities)

        driver.get('https://www.baidu.com')
        driver.find_element_by_id('kw').send_keys('selenoid')
        time.sleep(2)

    def test_baidu_opera(self):
        capabilities = {
            # 测试用例名称
            "name": "test_baidu_opera",
            # 浏览器类型
            "browserName": "opera",
            # 版本号
            "version": "79.0",
            # 开启远程VNC实时画面
            "enableVNC": True,
            # 开启视频录制
            "enableVideo": True,
            # 保存视频名称
            "videoName": "test_baidu_opera.mp4",
            # opera浏览器需要额外参数
            "operaOptions": {"binary": "/usr/bin/opera"}
        }
        driver = webdriver.Remote(
            command_executor="http://10.118.80.65:4444/wd/hub",
            desired_capabilities=capabilities)

        driver.get('https://www.baidu.com')
        driver.find_element_by_id('kw').send_keys('selenoid')
        time.sleep(2)

给selenoid container add proxy的方法:
https://github.com/aerokube/selenoid/issues/139
https://github.com/serenity-bdd/serenity-core/issues/2058

-ea
-Dwebdriver.remote.url=http://shcallencdfrh76vm02.hpeswlab.net:4444/wd/hub
-Dstory.names=DemoUIIdmAdmin
-Dwebdriver.remote.driver=chrome
-Dsuite.installer.url=https://autorh78vm00.hpeswlab.net:3000/ 
-Dui.demo.singleinstall.master=shcrh75vm00.hpeswlab.net
-Dchrome.switches="--ignore-certificate-errors"
-Dserenity.driver.capabilities="enableVNC:true;enableVideo:true;videoName:"DemoUIIdmAdmin.mp4";enableLog:true;logName:"DemoUIIdmAdmin.log";name:"DemoUIIdmAdmin""
-Dserenity.proxy.http=web-proxy.us.softwaregrp.net
-Dserenity.proxy.http_port=8080
-Dserenity.proxy.ssl=web-proxy.us.softwaregrp.net
-Dserenity.proxy.sslProxyPort=8080

My actions:

  1. Set proxy via systemd: https://docs.docker.com/config/daemon/systemd/
  2. Start Selenoid via cm:
./cm selenoid cleanup && ./cm selenoid configure --vnc --browsers "firefox;chrome;opera" --last-versions 5 --tmpfs 128 && ./cm selenoid start --port 4444 --args "-limit 5 -service-startup-timeout 90s -session-attempt-timeout 90s -session-delete-timeout 90s -timeout 120s"

  1. Start selenoid-ui via docker (not cm), when using cm --> selenoid-ui still not up connection with selenoid
DOCKER_GATEWAY_ADDR=`docker inspect selenoid -f {{.NetworkSettings.Gateway}}`
echo $DOCKER_GATEWAY_ADDR
docker run -d --name selenoid-ui -p 8080:8080 aerokube/selenoid-ui --selenoid-uri http://${DOCKER_GATEWAY_ADDR}:4444

  1. Set selenium proxy capabilities
for execute selenoid container (with proxy):
docker run --rm -e HTTP_PROXY=proxy:8080 -v /var/run/docker.sock:/var/run/docker.sock -v ${HOME}:/root aerokube/cm:latest selenoid start --vnc --tmpfs 128
for execute selenoid-ui container (without proxy)
docker run --rm -d --name selenoid-ui --link selenoid -p 8080:8080 aerokube/selenoid-ui --selenoid-uri=http://selenoid:4444
Ui container was able to obtain data from selenoid. I saw a list of browsers on the UI:

Passing a Capabilities object to the ChromeDriver() constructor is deprecated. You can find new official doc here.

ChromeOptions chromeOptions = new ChromeOptions();

Proxy proxy = new Proxy();
proxy.setAutodetect(false);
proxy.setHttpProxy("http_proxy-url:port"); 
proxy.setSslProxy("https_proxy-url:port");
proxy.setNoProxy("no_proxy-var");

chromeOptions.setCapability("proxy", proxy); 
driver = new ChromeDriver(chromeOptions);

https://www.selenium.dev/selenium/docs/api/py/_modules/selenium/webdriver/common/proxy.html


    DIRECT = ProxyTypeFactory.make(0, 'DIRECT')  # Direct connection, no proxy (default on Windows).
    MANUAL = ProxyTypeFactory.make(1, 'MANUAL')  # Manual proxy settings (e.g., for httpProxy).
    PAC = ProxyTypeFactory.make(2, 'PAC')  # Proxy autoconfiguration from URL.
    RESERVED_1 = ProxyTypeFactory.make(3, 'RESERVED1')  # Never used.
    AUTODETECT = ProxyTypeFactory.make(4, 'AUTODETECT')  # Proxy autodetection (presumably with WPAD).
    SYSTEM = ProxyTypeFactory.make(5, 'SYSTEM')  # Use system settings (default on Linux).
    UNSPECIFIED = ProxyTypeFactory.make(6, 'UNSPECIFIED')  # Not initialized (for internal use).

如何解决 session timeout的问题:

-service-startup-timeout 30s - Docker container or driver process startup timeout
-session-attempt-timeout 30s - New session attempt timeout, i.e. POST /wd/hub/session HTTP request timeout.
-session-delete-timeout 10s - Session removal HTTP request timeout
-timeout 60s - Session idle timeout, i.e. time between separate HTTP requests to Selenium API which automatically causes session to be closed.

https://github.com/aerokube/selenoid/issues/210
https://github.com/aerokube/selenoid/issues/262

如何解决selenoid container 内部Chrome文件上传的问题:

1.  Manually edit `browsers.json` of working Selenoid and add `"volumes": ["/home/app/uploadFiles:/home/app/uploadFiles"]` as shown here: [https://aerokube.com/selenoid/latest/#_browsers_configuration_file](https://aerokube.com/selenoid/latest/#_browsers_configuration_file)
2.  Restart Selenoid container

https://github.com/aerokube/selenoid/issues/561
https://github.com/aerokube/selenoid/issues/76
https://saucelabs.com/blog/selenium-tips-uploading-files-in-remote-webdriver

browsers.json 中 browsers 的设置:
https://aerokube.com/images/latest/#_browser_image_information

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容