lc2
v1.5.11
Published
browser automation library
Downloads
75
Readme
#lemoncase2
It's a tiny language for writing tests.
Install
npm install --save lc2
Grammar
All statements must end with a semicolon.
exampleclick 'here';
Identifier
- Similar to JavaScript
var a = 1;
- You do not have to use 'var' keyword (there's no type check currently, haha)
a = 1; a = true;
Datatype
Below is a list of data type you can use
- number (e.g., 0.12, 1e-3)
- string (e.g., 'you', "me")
- Boolean (true/false)
- RegExp
RegExp will be used as it is, to match against a string, if it is used with ~~
operator.
Otherwise it will generate a random string based on the regexp pattern (use randexp underneath).
Use it if you want to do some fuzzy testing.
Macro
Macro is the global setting for your case during runtime.
#TIMES [times:number|1]
// The case will run 10 times.
#TIMES 10
#INTERVAL [interval:number|3000]
// The case will wait 1000ms before start another one.
#INTERVAL 1000
#CLOCK [clock:number>5|10]
// The frequency of the engine, the engine will tick per 30ms.
#CLOCK 30
#SCREEN [width:number],[height:number]
// Set the screen to width 800px and height 600px.
#SCREEN 800,600
#AUTOSCROLL
// Auto scroll the window when trigger an instruction.
#AUTOSCROLL
Instruction keyword
[action] [selector:string] by/to [params|params1,params2]
Simulate an action
assert [exp][ in [overtime:number]]
Assert that the expression is true[ within overtime].
wait [time:number]
Wait a few time like a person.
log [exp]
Record the value of an expression.
jumpto [exp]
Jump to a URL.
refresh
Refresh the page.
process [identifier] { [statements] }
Define a test process.
Action keyword
You can not dispatch any event to an <iframe>
element, so your instruction will not take effect on those elements.
Any error occurred during execution will be recorded in the log.
For example, if your target element does not exist, or you can't dispatch any action to target element.
input
Type a string into an element like <input type="text" />
or <textarea>
.
// <input id="link" class="case" />
input 'div#link.case' by "hello world.";
click
Click a HTML element.
// <a href="...">Text</a>
click 'a';
// <a id="link" href="...">Text</a>
click 'a#link';
dblclick
Double click a HTML element.
// <div id="link" class="case">Text</div>
dblclick 'div#link.case';
rclick
Right click a HTML element.
// <div id="link" class="case">Text</div>
rclick 'div#link.case';
select [selector:string] by [value:string|index:number]
Select a <option> in <select> by value|index. (only works on <select> element)
// <select>
// <option value="a"></option> // index:0
// <option value="b"></option> // index:1 <-- this will be selected
// <option value="c"></option> // index:2
// </select>
select "select" by 1;
// OR
select "select" by "b";
movein - beta
Trigger move enter events on a HTML element.
moveout - beta
Trigger move leave events on a HTML element.
scroll - beta
Scroll to a specific position or element on page.
Expression
- Expression is what you will expect in any c-like language
Extended selector
When using a selector, you can use a string that is compatible with the following
- Native querySelectorAll.
- jQuery Sizzle ^2.3.0.
- It means you can use some useful pseudo class like :contain().
- iframe selector
<
that select elements inside<iframe>
*
<!-- From top frame -->
<iframe id="top">
#document
<html>
<body>
<iframe id="project">
#document
<html>
<body>
<a href="./">link</a> <!-- select it. -->
<b>link</b>
</body>
</html>
</iframe>
</body>
</html>
</iframe>
iframe < iframe < a
* < * < a
#top < #project < a
* < #project < a:contain(link)
Selector operator
<# [selector:string] />
// Get the number of elements that matches the selector.
// <div id="link" class="case">Text</div>
log <# 'div'/>; // Output 1
log <# '#link'/>; // Output 1
log <# '#no'/>; // Output 0
<@ [selector:string] />
// Get the innerHTML of the 1st element that matches the selector.
// The result is a string or `false` if there's no match.
// <div id="link" class="case">Text</div>
log <@ 'div'/>; // Output Text
log <@ '#link'/>; // Output Text
log <@ '#no'/>; // Output false
<! [selector:string] />
// Check for element visibility.
// The result is `true` if such element exist && width > 0 && height > 0.
// otherwise it is false
// <div id="link" class="case">Text</div>
// <div id="link2" style="display:none">Text</div>
log <@ 'a'/>; // Output false
log <@ 'div'/>; // Output true
log <@ '#link2'/>; // Output false
Compare operator
[exp_A:string] ~~ [exp_B:string|RegExp]
- exp_B is not a string or RegExp, false.
- exp_A is not a string, false.
- exp_A is not matched to exp_B(RegExp), false.
- exp_A has a sub string like exp_B, true.
- exp_A is matched to exp_B(RegExp), true.
[exp_A:string] !~ [exp_B:string|RegExp]
- !(exp_A ~~ exp_B).
filter operator
Filter a expression, usually a string.exp|filter(arg1, arg2, ...)[|filter(arg1, arg2, ...)...]
example:
"string" | substr(1, -1) | slice(0, 2);
Scope
- Global scope. So there is no
return
keyword in LC2. Aprocess
has no parameter. process main
is the program's entry.
Example
Here are some examples for you to get started with the language.
Hello, world
process main {
log 'hello world';
}
A typical test case
A test case that runs only once.
#TIMES 1
process main {
jumpto '[URL]';
wait 2000;
...
[statement]
...
}
Typical examples
Test a user registration page.
#TIMES 1
#INTERVAL 3000
process main {
jumpto '/#/register';
wait 2000;
// There are 5 text boxes.
assert <#'#userInfoFrom input'/> === 5;
// There is a captcha svg.
assert <!"img.pull-right"/>;
// There is a login button.
assert <!"#userInfoFrom > button.btn"/>;
// There is a home button.
assert <@"#global-back-button"/> ~~ 'glyphicon-home';
userNameRule = "#name-rule > div";
// Input username length < 6
input "#account-name" by '12345';
assert <@userNameRule/> ~~ '不少于6个字符';
// Input username length > 16
input "#account-name" by /\w{17}/;
assert <@userNameRule/> ~~ '不超过16个字符';
// Username is blank
input "#account-name" by '';
assert <@userNameRule/> ~~ '用户名不为空';
// Username is repeat.
input "#account-name" by 'active';
click "#account-email";
assert <!"#name-repeat"/> && <@"#name-repeat"/> ~~ '用户名重复' in 2000;
// Blank email.
emailRule = "#email-rule > div";
input "#account-email" by '';
assert <@emailRule/> ~~ '邮箱不为空';
// Error email
input "#account-email" by 'fdsf';
assert <@emailRule/> ~~ '请输入正确格式的邮箱';
// Repeat email
input "#account-email" by '[email protected]';
click "#account-password";
assert <!"#emial-repeat"/> && <@"#emial-repeat"/> ~~ '邮箱重复' in 2000;
// Input password length < 8
input "#account-password" by /\w{1,7}/;
assert <@"#password-rule > div"/> ~~ '不少于8个字符';
// Input password length >20
input "#account-password" by /\w{20,}/;
assert <@"#password-rule > div"/> ~~ '不超过20个字符';
// Blank password
input "#account-password" by '';
assert <@"#password-rule > div"/> ~~ '密码不为空';
// Error char.
input "#account-password" by '测试测试测试测试';
assert <@"#password-rule > div"/> ~~ '您输入了非法字符';
// Password !== Confirm
password = /\w{10}/;
input "#account-password" by password;
input "#account-password-confirm" by password + 'a';
assert <@"#confirm-rule > div"/> ~~ '两次输入不一致';
}