lc2

browser automation library

Usage no npm install needed!

<script type="module">
  import lc2 from 'https://cdn.skypack.dev/lc2';
</script>

README

http://lemonce.github.io/lemoncase2

lemoncase2

It's a tiny language for writing tests.

Build Status

Install

npm install --save lc2

Grammar

All statements must end with a semicolon.
example
click '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. A process 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 'lichao@or-change.cn';
    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"/> ~~ '两次输入不一致';
    
}