SlimerJS 是一个提供给
Web 开发人员,可通过脚本编程控制的浏览器。它可以让你使用Javascript 脚本操纵一个网页:打开一个网页,点击链接,修改的内容等,这对于做
功能测试,页面自动机,网络监控,屏幕捕获等是非常有用的。
事实上,它是类似 PhantomJS 的一个工具,但是 SlimerJS 只能运行在 Gecko (Firefox)上而不是Webkit。SlimerJS 提供几乎和 PhantomJS 相同的 API,高度兼容 PhantomJS。SlimerJS 不仅是 PhantomJS 的一个克隆,还包含额外的功能。
SlimerJS 兼容 CasperJS 1.1 beta!!
示例代码:
var webpage = require('webpage').create(); webpage .open('http://somewhere') // loads a page .then(function(){ // executed after loading // store a screenshot of the page webpage.viewportSize = { width:650, height:320 }; webpage.render('page.png', {onlyViewport:true}); // then open a second page return webpage.open('http://somewhere2'); }) .then(function(){ // click somewhere on the second page webpage.sendEvent("click", 5, 5, 'left', 0); slimer.exit() }); |
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
安装Eclipse插件
For Eclipse 3.4 and above, enter http://beust.com/eclipse.
For Eclipse 3.3 and below, enter http://beust.com/eclipse1.
<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>com.homeinns.web</groupId> <artifactId>homeinns-testng</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>homeinns-testng</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.1.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.16</version> <configuration> <suiteXmlFiles> <suiteXmlFile>testng.xml</suiteXmlFile> <!-- <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> --> </suiteXmlFiles> </configuration> </plugin> </plugins> </build> </project> |
配置TestNg suite
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="Suite" parallel="none"> <!--enabled="true"让测试生效,也可根据情况关闭某些测试 --> <test name="Test" enabled="true"> <!--指定参数 --> <parameter name="Name" value="Irving" /> <parameter name="Sex" value="Man" /> <!--指定测试包 --> <packages> <package name="com.homeinns.web.testng.*" /> </packages> <!--指定测试类 --> <classes> <class name="com.homeinns.web.testng.AppTest" /> </classes> </test> <!-- Test --> </suite> <!-- Suite --> TestNg注解配置 public class NgTest { @Test public void f() { } @Test( // 在指定的时间内启用3个线程并发测试本方法10次 threadPoolSize = 3, invocationCount = 10, timeOut = 10000, // 等待测试方法t0测试结束后开始本测试 dependsOnMethods = { "f" }, // 指定测试数据源CLASS和数据源名称(参考注解@DataProvider),返回几条数据会跑测试方法几次 dataProvider = "generate", dataProviderClass = GeneratorRandomNum.class, // 分组名称 groups = { "checkin-test" }) // 读取配置文件中的参数,配置如上,用@Optional设置默认值 @Parameters({ "Name" }) public void f1(@Optional("name") String name) { } } |
测试报告
运行测试后 在my-testng/test-output/ 目录下(maven \target\surefire-reports)
gradle配置
subprojects { apply plugin: 'java' // Disable the test report for the individual test task test { reports.html.enabled = false } } task testReport(type: TestReport) { destinationDir = file("$buildDir/reports/allTests") //Include the results from the `test` task in all subprojects reportOn subprojects*.test }Grouping TestNG tests test { useTestNG { excludeGroups 'integrationTests' includeGroups 'unitTests' } } |
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
TestNG支持对Junit4测试代码的自动重构(@test tag)
其中对于数组比较,
Junit: assertEquals("msg", expected, actual);
TestNG: AssertJUnit.assertEquals("msg", expected, actual);
似乎这是一个delegate的处理方式。不过执行的时候报错:
java.lang.AssertionError: correct tokens expected:<[Ljava.lang.String;@941db6> but was:<[Ljava.lang.String;@2acc57> at org.testng.AssertJUnit.fail(AssertJUnit.java:59) at org.testng.AssertJUnit.failNotEquals(AssertJUnit.java:364) at org.testng.AssertJUnit.assertEquals(AssertJUnit.java:80)... |
Baidu上一无所获,于是google. 确认这是一个存在的bug。如下的link讨论的是int[]的情况。估计string[]的情况类似。
http://code.google.com/p/testng/issues/detail?id=4
暂时的处理方法:继续用junit的assert方法。但是用testNG来识别@test标记。
//import org.testng.AssertJUnit;
import static org.junit.Assert.*;
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
在项目中加入junit-x.x.jar包
在MyEclipse在package上右键 可以找到 Junit
Test Case
只要在合适的包中 一般在对应待测试类的test包中 新建Junit Test Case
然后可以选择 对哪个类 的哪个方法进行测试
MyEclipse就会自动生成测试类框架
如果要用4以上推出的 assertThat(T actual,org.hamcrest.Matcher<T> matcher)方法
需要引入hamcrest中的hamcrest-core.jar hamcrest-library.jar两个包
由assertThat第二个参数名可以看出 是由hamcrest定义的
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
Nikto是一款开放源代码的、功能强大的
WEB扫描评估软件,能对web服务器多种安全项目进行
测试的扫描软件,能在230多种服务器上扫描出 2600多种有潜在危险的文件、CGI及其他问题,它可以扫描指定主机的WEB类型、主机名、特定目录、COOKIE、特定CGI漏洞、返回主机允许的 http模式等等。它也使用LibWhiske库,但通常比Whisker更新的更为频繁。Nikto是网管安全人员必备的WEB审计工具之一。
Nikto最新版本为2.0版,可以去官方下载
Nikto是基于PERL开发的程序,所以需要PERL环境。Nikto支持
Windows(使用ActiveState Perl环境)、Mac OSX、多种
Linux 或Unix系统。Nikto使用SSL需要Net::SSLeay PERL模式,则必须在Unix平台上安装OpenSSL。具体的可以参考nikto的帮助文档。
从官方网站上下载nikto-current.tar.gz文件,在Linux系统解压操作:
tar -xvf nikto-current.tar.gz
gzip -d nikto-current.tar
解压后的结果如下所示:
Config.txt、docs、kbase、nikto.pl、plugins、 templates
Nikto的使用说明:
Nikto扫描需要主机目标IP、主机端口。默认扫描的是80端口。扫描主机目标IP地址可以使用选项-h(host)。下面将扫描IP为192.168.0.1的TCP 80端口,如下所示:
perl nkito.pl –h 192.168.0.1
也可以自定义扫描的端口,可以使用选项-p(port),下面将扫描IP为192.168.0.1的TCP 443端口,如下所示:
perl nikto.pl –h 192.168.0.1 –p 443
Nikto也可以同时扫描多个端口,使用选项-p(port),可以扫描一段范围(比如:80-90),也可以扫描多个端口(比如:80,88,90)。下面扫描主机的80/88/443端口,如下所示:
Perl nikto.pl –h 192.168.0.1 –p 80,88,443
如果运行Nikto的主机是通过HTTP proxy来访问
互联网的,也可以使用代理来扫描,使用选项-u(useproxy)。下面将通过HTTP proxy来扫描,如下所示:
Perl nikto.ph –h 192.168.0.1 –p 80 –u
Nikto的更新:
Nikto的升级可以通过-update的命令来更新插件和
数据库,如下所示:
Perl nikto.ph –update
也可以通过从网站下载来更新插件和数据库:[url]http://updates.cirt.net/[/url]
Nikto的选项说明:
-Cgidirs
扫描CGI目录。
-config
使用指定的config文件来替代安装在本地的config.txt文件
-dbcheck
选择语法错误的扫描数据库。
-evasion
使用LibWhisker中对IDS的躲避技术,可使用以下几种类型:
1.随机URL编码(非UTF-8方式)
2.自选择路径(/./)
3.虚假的请求结束
4.长的URL请求
5.参数隐藏
6.使用TAB作为命令的分隔符
7.大小写敏感
8.使用Windows路径分隔符\替换/
9.会话重组
-findonly
仅用来发现HTTP和HTTPS端口,而不执行检测规则
-Format
指定检测报告输出文件的格式,默认是txt文件格式(csv/txt/htm)
-host
目标主机,主机名、IP地址、主机列表文件。
-id
ID和密码对于授权的HTTP认证。格式:id:password
-mutate
变化猜测技术
1.使用所有的root目录测试所有文件
2.猜测密码文件名字
3.列举Apache的用户名字(/~user)
4.列举cgiwrap的用户名字(/cgi-bin/cgiwrap/~user)
-nolookup
不执行主机名查找
-output
报告输出指定地点
-port
扫描端口指定,默认为80端口。
-Pause
每次操作之间的延迟时间
- Display
控制Nikto输出的显示
1.直接显示信息
2.显示的cookies信息
3.显示所有200/OK的反应
4.显示认证请求的URLs
5.Debug输出
-ssl
强制在端口上使用SSL模式
-Single
执行单个对目标服务的请求操作。
-timeout
每个请求的超时时间,默认为10秒
-Tuning
Tuning 选项控制Nikto使用不同的方式来扫描目标。
0.文件上传
1.日志文件
2.默认的文件
3.信息泄漏
4.注射(XSS/Script/HTML)
5.远程文件检索(Web 目录中)
6.拒绝服务
7.远程文件检索(服务器)
8.代码执行-远程shell
9.SQL注入
a.认证绕过
b.软件关联
g.属性(不要依懒banner的信息)
x.反向连接选项
-useproxy
使用指定代理扫描
-update
更新插件和数据库
例子:使用Nikto扫描目标主机10.0.0.12的phpwind论坛网站。
Perl nikto.pl –h 10.0.0.12 –o test.txt
通过上面的扫描结果,我们可以发现这个Phpwind论坛网站,是在windows操作系统上,使用Apache/2.2.4版本,Php/5.2.0版本,以及系统默认的配置文件和路径等。
综上所述,Nikto工具可以帮助我们对Web的安全进行审计,及时发现网站存在的安全漏洞,对网站的安全做进一步的扫描评估。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
常规的对username/passwprd进行payload测试,我想大家应该没有什么问题,但对于Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=这样的问题,很多朋友疑惑了.
之前,我记得我介绍过
burpsuite的intruder功能(BurpSuite之
SQL Injection),想必很多人没什么印象,在此,以HTTP brute重提intruder功能.
以下面案例进行说明(只作演示之用,具体以自己的目标为准):
Auth=dXNlcjpwYXNzd29yZA==处,也就是我们的关键位置.
那么具体该如何做呢?大致操作过程如下:
1.解密base64字符串
3.利用payload进行测试
1.解密验证用的base64字符串
解密后的字符串为:
Auth=user:password
问题来了,针对user:password这种形式的字符串,我们该如何设置payload呢?
想必很多人在此处了费尽心思。为了解决这个问题,接下来请看第二部分。
2.生成测试用的payload
对于这种格式,无法利用burpsuite顺利的完成测试,那个就需要丰富对应的payload了.
我的做法就是,利用burpsuite生成我要的payload文本.
Auth=§user§§:§§password§
设置3处payloads,
1 ------ §user§
2 ------ §:§
3 ------ §password§
然后根据intruder自带的battering ram/pitchfork/cluster bomb生成payloads(根据自己的需求生成)
我在此处选择以cluster bomb为例,利用intruder生成需要的payloads,然后保存到文本文件中.
3.利用payload进行测试
测试的时候,我们选用sniper,我们只需一个payload变量
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
简介:
Nikto是一款开放源代码的、功能强大的
WEB扫描评估软件,能对web服务器多种安全项目进行
测试的扫描软件,能在230多种服务器上扫描出 2600多种有潜在危险的文件、CGI及其他问题,它可以扫描指定主机的WEB类型、主机名、特定目录、COOKIE、特定CGI漏洞、返回主机允许的 http模式等等。它也使用LibWhiske库,但通常比Whisker更新的更为频繁。
以下在10.46.170.167上部署测试:
下载最新版Nikto
http://211.138.156.198:81/1Q2W3E4R5T6Y7U8I9O0P1Z2X3C4V5B/www.cirt.net/nikto/nikto-2.1.5.tar.bz2
安装Nikto
[root@gyfd ~]# tar jxvf nikto-2.1.5.tar.bz2
[root@gyfd ~]# mv nikto-2.1.5 /usr/local/
使用
(1). 基本测试
[root@gyfd ~]# cd /usr/local/nikto-2.1.5
----------------------------------------------------------------------------------------------------------------------------
[root@gyfd nikto-2.1.5]# perl nikto.pl -h 10.46.169.24 -p80 -output text.txt
说明:-h 指定被扫描的IP或者主机名
-p 指定被扫描的端口,没有指定则默认80,可指定扫描范围或者多个端口
-output 指定扫描结果保存文件。可保存的格式为text, CSV, HTML, XML, NBE等。
扫描结果:(会在终端中显示,同时保存到指定的文件中)
[root@gyfd nikto-2.1.5]# perl nikto.pl -h10.46.169.24 -p 80 -output text.txt
- ***** SSL support not available (see docsfor SSL install) *****
- Nikto v2.1.5
---------------------------------------------------------------------------
+ Target IP: 10.46.169.24
+ Target Hostname: 10.46.169.24
+ Target Port: 80
+ Start Time: 2014-08-20 17:08:58 (GMT8)
---------------------------------------------------------------------------
+ Server: Apache
+ Uncommon header 'x-frame-options' found,with contents: SAMEORIGIN
+ No CGI Directories found (use '-C all' toforce check all possible dirs)
+ Allowed HTTP Methods: GET, HEAD, POST, OPTIONS
+ 6544 items checked: 0 error(s) and 2item(s) reported on remote host
+ End Time: 2014-08-20 17:09:15 (GMT8) (17seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
---------------------------------------------------------------------------------------------------------------------------
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
策略:
1、loadruner中是不支持File数据类型的,所以用int或者long来声明一个文件;
2、关于文件处理的几种方法。《关于C语言的fprintf与fwrite使用区别》这篇
文章中解释得很详细,选用fprintf 方法;
3、fopen()方法。可参考:《LoadRunner下如何进行文件的操作》
fopen(filename,"a")) :文件存在,就覆盖写,不存在会先创建。为了不让它每次覆盖,我在fprintf()中使用了“%s\n”,每次都换行追加;
有人试过fopen(filename,"a+")) ,这样写的效果是一样的。
实现:
Action{ long file_stream; char *filename = "c:\\001.txt"; soap_request(此段省略,即webservice协议的两种生成脚本方式); // 将response出力 lr_message(lr_eval_string("Response is: \n {response}")); // 此处response是无须定义的,原因自己理解 // 取所需的依赖字段,关键函数lr_xml_get_values lr_xml_get_values("XML = {response}", "ValueParam = ValueParam ", "Query = XXX", LAST); // 此函数自行理解使用方法 // 本文重点 写文件 if((file_stream = fopen(filename,"a")) == NULL){ lr_error_message("Cannot open %s",filename); return -1; } fprintf(file_stream,"%s\n",lr_eval_string("{ValueParam }")); fclose(file_stream); return 0; } |
总结:不是很难的代码,只是编写过程中学会举一反三,不拘泥于一种文件操作方法。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
代码和selenium driver相同 只是 启动环境方式不同。至少启动一个hub 一个 node 。如需要多个,可以使用端口进行区分。
java -jar selenium-server-standalone-x.xx.x.jar -role node -port 5555 java -jar selenium-server-standalone-x.xx.x.jar -role node -port 5556 java -jar selenium-server-standalone-x.xx.x.jar -role node -port 5557 |
代码如下
WebDriver wd = new RemoteDriver("http://localhost:4444/wd/hub", aDesiredcap); //test01: 只匹配 Windows下的ie来执行此用例,版本不限;多个版本匹配成功时优先级暂未知 DesiredCapabilities aDesiredcap = DesiredCapabilities(); aDesiredcap.setBrowserName("internet explorer") aDesiredcap.setVersion("") aDesiredcap.setPlatform(Platform.WINDOWS) WebDriver wd = new RemoteDriver("http://localhost:4444/wd/hub", aDesiredcap); wd.doSomething() //test02: 只匹配linix下的firefox的版本为22的浏览器执行用例; DesiredCapabilities aDesiredcap = DesiredCapabilities("firefox", "22", Platform.LINUX); WebDriver wd = new RemoteDriver("http://localhost:4444/wd/hub", aDesiredcap); wd.doSomething() //test03: 只匹配MAC下的safari浏览器执行,版本不限 DesiredCapabilities aDesiredcap = DesiredCapabilities.safari(); aDesiredcap.setPlatform(Platform.MAC) WebDriver wd = new RemoteDriver("http://localhost:4444/wd/hub", aDesiredcap); wd.doSomething() //test04: 只匹配chrome浏览器,任意平台,任意版本 DesiredCapabilities aDesiredcap = DesiredCapabilities.chrome(); aDesiredcap.setPlatform(Platform.ANY) WebDriver wd = new RemoteDriver("http://localhost:4444/wd/hub", aDesiredcap); wd.doSomething() |
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
说明:这些问答是从网上转载的,自己修改了其中的一些内容,如果大家兴趣,可以将大家在使用
Jmeter的时候碰到的问题写下来,我们一起补充到这个问答里面,共同努力完善jmeter的资料。
向服务器提交请求;从服务器取回请求返回的结果。
2. JMeter的作用?
JMeter可以用于
测试静态或者动态资源的性能(文件、Servlets、Perl脚本、
java对象、
数据库和查询、ftp服务器或者其他的资源)。JMeter用于模拟在服务器、网络或者其他对象上附加高负载以测试他们提供服务的受压能力,或者分析他们提供的服务在不同负载条件下的总性能情况。你可以用JMeter提供的图形化界面分析性能指标或者在高负载情况下测试服务器/脚本/对象的行为。
3. 怎样能看到jmeter提供的脚本范例?
在\JMeter\jakarta-jmeter-2.0.3\xdocs\demos目录下。
4. 怎样设置并发用户数?
选中可视化界面中左边树的Test Plan节点,单击右键,选择Add-> Thread Group,其中Number of Threads参数用来设置发送请求的用户数目。
5. JMeter的运行指示?
Jmeter在运行时,右上角有个单选框大小的小框框,运行是该框框为绿色,运行完毕后,该框框为白色。
6. User Parameters的作用是什么?
提高脚本可用性
7. 在result里会出现彩色字体的http response code,说明什么呢?
Http response code是http返回值,彩色字体较引人注目,可以使用户迅速关注。象绿色的302就说明在这一步骤中,返回值取自本机的catch,而不是
server。
8. 怎样计算Ramp-up period时间?
Ramp-up period是指每个请求发生的总时间间隔,单位是秒。如果Number of Threads设置为5,而Ramp-up period是10,那么每个请求之间的间隔就是10/5,也就是2秒。Ramp-up period设置为0,就是同时并发请求。
9. Get和Post的区别?
他们是http协议的2种不同实现方式。Get是指server从Request URL取得所需参数。从result中的request中可以看到,get可以看到参数,但是post是主动向server发送参数,所以一般看不到这些参数的。
10. 哪些原因可能导致error的产生?
a. Http错误,包括不响应,结果找不到,数据错误等等;
b. JMeter本身原因产生的错误。
11. 为什么Aggregate Report结果中的Total值不是真正的总和?
JMeter给结果中total的定义是并不完全指总和,为了方便使用,它的值表现了所在列的代表值,比如min值,它的total就是所在列的最小值。下图就是total在各列所表示的意思。
12. JMeter的Thread Number是提供多个不同用户并发的功能么?
不是,Thread Number仅仅是指并发数,如果需要实现多个不同用户并发,我们应该采用其它方法,比如通过在jmeter外建立csv文件的方法来实现。
13. 同时并发请求时,若需要模拟不同的用户同时向不同的server并发请求,怎样实现呢?
方法很灵活,我们可以将不同的server在thread里面预先写好。或者预先将固定的变量值写入csv文件,这样还可以方便修改。然后将文件添加到User Parameters。
14. User Parameter中的DUMMY是什么意思?
当其具体内容是${__CSVRead(${__property(user.dir)}${FILENAME},next())}时用来模拟读文件的下一行。
15. 当测试对象在多server间跳转时,应该怎样处理?
程序运行时,有些http和隐函数会携带另外的server IP,我们可以从他们的返回值中获取。
16. 为何测试对象是http和https混杂出现?
Https是加密协议,为了安全,一般不推荐使用http,但是有些地方,使用https过于复杂或者较难实现,会采用http协议。
17. Http和https的默认端口是什么?
Apache server (Http)的默认端口是80;
SSL (Https)的默认端口是443。
18. 为何在run时,有些页面失败,但是最后不影响结果?
原因较多,值得提及的一种是因为主流页面与它不存在依赖关系,所以即使这样的页面出错,也不会影响运行得到正常结果,但是这样会影响到测试的结果以及分析结果。
19. 为什么脚本刚开始运行就有错误,其后来的脚本还可运行?
在Thread Group中有相关设置,如果选择了continue,即使前面的脚本出现错误,整个thread仍会运行直到结束。选择Stop Thread会结束当前thread;选择Stop Test则会结束全部的thread。推荐选项是Stop Thread。
20. 在Regular expression_r Extractor会看到Template的值是$1$,这个值是什么意思呢?
$1$是指取第一个()里面的值。如果Regular expression_r的数值有多个,用这种方法可以避免不必要的麻烦。
21. Regular expression_r中的(.*)是什么意思?
那是一个正则表达式(regular expression_r)。’.’等同于sql语言中的’?’,表示可有可无。’*’表示0个或多个。’()’表示需要取值。(.*)表达任意长度的字符串。
22. 在读取Regular expression_r时要注意什么?
一定要保证所取数值的绝对唯一性。
23. 怎样才能判断什么样的情况需要添加Regular expression_r Extractor?
检查Http Request中的Send Parameters,如果有某个参数是其前一个page中所没有给出的,就要到原文件中查找,并添加Regular expression_r Extractor到其前一page的http request中。
24. 在自动获取的脚本中有时会出现空的http request,是什么意思呢?
是因为在获取脚本时有些错误,是脚本工具原因。在run时这种错误不参与运行的。
25. 在运行结果中为何有rate为N/A的情况出现?
可能因为JMeter自身问题造成,再次运行可以得到正确结果。
26. 常用http错误代码有哪些?
400无法解析此请求。
403禁止访问:访问被拒绝。
404找不到文件或目录。
405用于访问该页的HTTP动作未被许可。
410文件已删除。
500服务器内部错误。
501标题值指定的配置没有执行。
502 Web服务器作为网关或代理服务器时收到无效的响应。
27. Http request中的Send Parameters是指什么?
是指code中写定的值和自定义变量中得到的值,就是在运行页面时需要的参数。
28. Parameters在页面中是不断传递的么?
是的。参数再产生后会在页面中一直传递到所需页面。所以我们可以在动态参数产生时捕获它,也可以在所需页面的上一页面捕获。(但是这样可能有错误,最好在产生页面获取)
29. 在使用JMeter测试时,是完全模拟用户操作么?造成的结果也和用户操作完全相同么?
是的。JMeter完全模拟用户操作,所以操作记录会全部写入DB.在运行失败时,可能会产生错误数据,这就取决于脚本检查是否严谨,否则错误数据也会进入DB,给程序运行带来很多麻烦。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters