Author: Cold grass micro letter: HancAO97 introduction: a different north to the programmer (work 10 months of young programmer), welcome to add micro letter criticism and correct communication technology or play together about dinner, or blind date wuvuvuu, 520 was miserable

background

Why do it?

We recently in the new alignment of the project, by measuring phase had a lot of problems, the description of the specific questions I am not here too much, anyway, eventually caused the front students mental tired, restless, ha ha ha ha, I’m not complaining about somebody here also, but we will encounter problems to think about how to solve the problem ~). So I was thinking about how we can make the experience more comfortable during these stages. After a little thought, I’m going to provide some automation capabilities that will allow us to automate test cases and monitor interface information as we go through test cases, presenting problematic interface calls to our developers after we go through test cases.

Github:github.com/CodingCommu…

Don’t get me wrong, in fact, the development has not been completed, I just at leisure, idle time for a part of the technical research, here and you introduce my ideas.

How to do it?

Before I say what I think, I want to say what I want to do:

  • Provide the ability to automate test cases/automate self tests
  • Interface monitoring capability during operation

Ok, now that I understand my needs, I can think about how to achieve them.At the very beginning, I made a diagram like this, in which there are four layers from top to bottom. I will introduce each layer to you:

  • Level 1: I described the functions we wanted to do, mainly to do front-end lightweight automation testing, includingSelf test automationandThe interface test
  • The second layer: the second layer actually I explained that I divided this requirement into two parts, one isAutomation capacityPart of it isInterface monitoring capability. This article, of course, focuses on automation capabilities.
  • The third layer: I am on the second layerAutomation capacityandInterface monitoring capabilityDo the disassembly (PS: I will go into the automation capability part in detail later, interface monitoring bury the pit, next time ~)
  • Level 4: I would like my test automation tool to be presented asChrome plug-ins“Because I think the Chrome plugin is much easier to use and I can take advantage of what it offerswebRequestThe ability to observe and analyze the interface invocation and traffic information (to be investigated here).

Automated capability disassembly

The first thing I felt about what I was going to do was button sprites. What I want is that we can do it once, and then we can record the process, and then we just need to do it automatically. Ok, since we want something like a button Sprite, I’ll take this requirement apart:

  1. Recording (key point of this article)

As we might expect, the first step is to record the user’s actions. But the question we’re thinking about at this stage is how do we record? What information is recorded? What operations are recorded? How can records be easily automated?

  1. Operation process -> intermediate data

The ultimate purpose of recording is to reproduce the recorded information the next time we need to test ourselves or pass test cases, so in fact, we must convert the recorded information into some kind of data structure and save it locally.

  1. Intermediate data ->js script

You can convert the intermediate data into a JS script, which is a little easier to understand, which is to convert the intermediate data that was stored locally into a JS script

  1. Self-executing script

Js scripts are automatically executed to reproduce user operations. This is the final step in recreating the user’s actions.

Ok, now that the automation capability is simply dismantled, let’s get to the main point of this article. How do I record user actions?

User Operation recording

It’s not that difficult to record the user’s actions, but consider what information we need to record so that we can reproduce the user’s actions in subsequent operations. So I’m going to show you not only how I think about recording user actions, but also how I’m going to use the information I’ve recorded to reproduce user actions. The user operations I mainly introduce here include three types:

  • Click on the operation
  • Keyboard input
  • scroll

It looks like only three, but it covers a lot of scenarios in our business. Github:github.com/CodingCommu… You can view webapi.html including all the code for this blog

Tip: All click events, scroll events, keyboard input events need to be recorded at a point in time, offset from the start time, in order to correctly reproduce. I won’t cover recording time later in this article

The mouse to click

code

dom:

<button class="btn">
    nihao
</button>
Copy the code

js:

document.onclick = function (ev) {
    const x = ev.clientX;
    const y = ev.clientY;
    setTimeout(() = > {
        document.elementFromPoint(x, y).dispatchEvent(clickEvent);
    }, 2000)}document.getElementsByClassName('btn') [0].onclick = function () {
    console.log(11.1)}Copy the code

Interpretation of the

First of all, it’s not difficult to record the user click events, but I want the result to be more convenient in the intermediate data ->js script link in the previous article, so I have this principle:

Do not use the record class, ID, and so on to find the DOM element through the way to reproduce the operation. Expect to find elements directly through coordinate points!

So I’m going to introduce you to an Api that you may not have used before, but is especially useful in this scenario:

document.elementFromPoint(x, y)

If you look at my code, I’ll give youdocumentBind the click event and record itclientXandclientY.

A brief introduction to clientX:

I’m going to use the same thing that I did in the other articleclientXIn this paper.

MDN: MouseEvent.clientx is a read-only property that provides the horizontal coordinates (distinct from the page coordinates) of the application client area at the time the event occurred. For example, when you click on the top left corner of the client area, the clientX value of the mouse event will be 0, regardless of whether the page has horizontal scrolling.

After we recorded the click on the location of the us in the present stage, only need through the document. The elementFromPoint (x, y) again after getting elements for its dispatchEvent click event with respect to ok.

Related knowledge:

MouseEvent:developer.mozilla.org/zh-CN/docs/… DispatchEvent: developer.mozilla.org/zh-CN/docs/…

More tests:

<! - Used to test whether multiple types of click events can be distributed --><label><input name="Fruit" type="radio" value="" class="aaa" />apple</label>
<label><input name="Fruit" type="radio" value="" class="bbb" />peach</label>
<label><input name="Fruit" type="radio" value="" />banana</label>
<label><input name="Fruit" type="radio" value="" />pear</label>
<label><input name="Fruit" type="radio" value="" />other</label>
Copy the code
// Test whether radio click events can be distributed
document.getElementsByClassName('bbb') [0].dispatchEvent(clickEvent)
document.getElementsByClassName('aaa') [0].dispatchEvent(focusEvent)
Copy the code

Keyboard input

code

// Test the keyboard input event
// Let's assume that continuous keyboard input is the same input,
// All I need to do is save the input order, record the last element clicked, and change its value!! (innerHtml/innerText) (innerHtml/innerText) (innerHtml/innerText) To support rich text
document.onkeydown = function (ev) {
    console.log(ev)
}
const keyTestEl = document.getElementsByClassName('inputTest') [0];
keyTestEl.value = '11111'
Copy the code

Interpretation of the

In fact, the keyboard recording my ideas is very simple, my above code test content can describe my ideas.

User operation process: The user clicks on an element, and then it keeps typing until it gets to the next oneClick event triggerThe end of user input is the end of the keyboard action event on the DOM element.

Reproducing keyboard input events:

In fact, according to the idea of flow chart, we can complete the recording and reappearance of the suggested keyboard events.

With the help of:document.onkeydownAnd by element typevalueorinnerHtmlProperties. Of course, also need to use the previousdocument.elementFromPoint(x, y)Gets and caches the element that was clicked.

That’s the end of the click event

Page scrolling (complicated scenarios)

code

Dom:

<div class="scroll-container">
    <div class="scroll-content">
        <div class="scroll-inner">Internal roll test</div>
    </div>
</div>
Copy the code

CSS:

body {
    height: 1800px;
}

.scroll-container {
    max-height: 500px;
    background: pink;
    overflow: auto;
}

.scroll-content {
    height: 800px;

}

.scroll-inner {
    width: 300px;
    height: 300px;
    background-color: purple;
}
Copy the code

Js:

let mouseX = 0;
let mouseY = 0;
let scrollStartEl = null; // This is used to record the starting element of the scroll, so as to ensure that the scrollTop is set for the element during the replay operation without any deviation
let scrollRecordList = [];
let scrollElementSet = new Set(a);// Universal throttling method
const throttle = function (cb, delay = 100) {
    let timer = null;
    return (ev) = > {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() = > {
            cb && cb(ev);
        }, delay)
    };
}

// Bind scroll events
setScrollWatcher = function (ev) {
    mouseX = ev && ev.clientX || mouseX;
    mouseY = ev && ev.clientY || mouseY;
    scrollStartEl = document.elementFromPoint(mouseX, mouseY);
    let el = scrollStartEl;
    while (el) {
        if (scrollElementSet.has(el)) {
            el = null;
        } else{ el.onscroll = throttle(recordScrollInfo); scrollElementSet.add(el); el = el.parentNode; }}};// Record the scrolling information
recordScrollInfo = function (ev) {
    let el = scrollStartEl;
    // Simple scrolling can also cause changes to the mouse's dom. SetScrollWatcher is also required to end the scrolling
    setScrollWatcher();
    let scrollRecordInfo = {
        mouseX: mouseX,
        mouseY: mouseY,
        scrollTopList: []}while (el) {
        scrollRecordInfo.scrollTopList.push(el.scrollTop);
        el = el.parentNode;
    }
    scrollRecordList.push(scrollRecordInfo);
    console.log(scrollRecordList)
}

// Bind the mouse movement event
document.onmousemove = throttle(setScrollWatcher);
Copy the code

Interpretation of the

Here, please follow my thoughts step by step:

  1. First of all, I givedocumentThe mouse movement event is bound and set for itThe throttle
// Bind the mouse movement event
document.onmousemove = throttle(setScrollWatcher);
Copy the code

I now have where the mouse stopped, and the idea is that if the user starts to scroll the mouse wheel now, the scrolling of the page is likely to occur on the element that corresponds to the mouse hover position and all of its ancestors!

  1. As I said, what I’m going to do now is to giveThe element corresponding to the mouse hover position and all of its ancestorsBind scroll events, i.esetScrollWatcherMethod does.
let mouseX = 0;
let mouseY = 0;
let scrollStartEl = null; // This is used to record the starting element of the scroll, so as to ensure that the scrollTop is set for the element during the replay operation without any deviation
let scrollElementSet = new Set(a);// Bind scroll events
setScrollWatcher = function (ev) {
    mouseX = ev && ev.clientX || mouseX;
    mouseY = ev && ev.clientY || mouseY;
    scrollStartEl = document.elementFromPoint(mouseX, mouseY);
    let el = scrollStartEl;
    while (el) {
        if (scrollElementSet.has(el)) {
            el = null;
        } else{ el.onscroll = throttle(recordScrollInfo); scrollElementSet.add(el); el = el.parentNode; }}};Copy the code

First of all, the mouse stops, I record the current mouse element in scrollStartEl, and then set the scroll event for it and all its ancestor elements (with throttling of course).

Optimization: In order to prevent repeated binding of scroll events, there is also an optimization by introducing a scrollElementSet, which terminates the loop if it exists. (Tip: Because an element can only have one direct parent)

  1. The next obvious step is to log the scrolling event after it has finished
recordScrollInfo = function (ev) {
    let el = scrollStartEl;
    // Simple scrolling can also cause changes to the mouse's dom. SetScrollWatcher is also required to end the scrolling
    setScrollWatcher();
    let scrollRecordInfo = {
        mouseX: mouseX,
        mouseY: mouseY,
        scrollTopList: []}while (el) {
        scrollRecordInfo.scrollTopList.push(el.scrollTop);
        el = el.parentNode;
    }
    scrollRecordList.push(scrollRecordInfo);
    console.log(scrollRecordList)
}
Copy the code

All I need to do is record the scrollTop of the scrollStartEl and all of its ancestors. Then, in the process of the double line operation, I will use the recorded mouseX and mouseY to find the scrollTop and set the scrollTop for it and all of its ancestors

Note:

  1. Simple scrolling can also cause changes to the mouse-related DOM, and setScrollWatcher is needed to end the scrolling
  2. Because the element corresponding to the mouse position may change after the scroll is over, it is really necessary to record the scroll start element before the scroll to prevent inconsistency in the element hierarchy when the scroll is repeated.

I feel this rolling record is still a little SAO, ha ha ha, I am so smelly shameless, ha ha ha ~

Complete code [convenient view]

The complete test code is here, if github is not convenient, you can directly copy and paste to try ~

<! DOCTYPEhtml>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
    <style type="text/css">
        body {
            height: 1800px;
        }

        .scroll-container {
            max-height: 500px;
            background: pink;
            overflow: auto;
        }

        .scroll-content {
            height: 800px;

        }

        .scroll-inner {
            width: 300px;
            height: 300px;
            background-color: purple;
        }

        .btn {
            margin-top: 1000px;
        }
    </style>
</head>

<body><! - Used to test whether multiple types of click events can be distributed --><label><input name="Fruit" type="radio" value="" class="aaa" />apple</label>
    <label><input name="Fruit" type="radio" value="" class="bbb" />peach</label>
    <label><input name="Fruit" type="radio" value="" />banana</label>
    <label><input name="Fruit" type="radio" value="" />pear</label>
    <label><input name="Fruit" type="radio" value="" />other</label><! - Used to test keyDown events --><input class ="inputTest" />
    <button class="btn">
        nihao
    </button>
    <div class="scroll-container">
        <div class="scroll-content">
            <div class="scroll-inner">Internal roll test</div>
        </div>
    </div>
    <script>
        /* * First step: Complete the click event, scroll event, keyboard input event record and reproduce. *TODO:To Do: Drag and drop events? * /
        const clickEvent = new MouseEvent('click');
        const focusEvent = new FocusEvent('focus', {view: window
        });

        // Universal throttling method
        const throttle = function (cb, delay = 100) {
            let timer = null;
            return (ev) = > {
                if (timer) {
                    clearTimeout(timer)
                }
                timer = setTimeout(() = > {
                    cb && cb(ev);
                }, delay)
            };
        }
        //TODO:All click events, scroll events, keyboard input events need to be recorded at a point in time offset from the start time in order to reproduce correctly
        // Test the click event
        document.onclick = function (ev) {
            const x = ev.clientX;
            const y = ev.clientY;
            setTimeout(() = > {
                document.elementFromPoint(x, y).dispatchEvent(clickEvent);
            }, 2000)}document.getElementsByClassName('btn') [0].onclick = function () {
            console.log(11.1)}// TODO:(to be verified) Test reproduces the scrolling event
        let mouseX = 0;
        let mouseY = 0;
        let scrollStartEl = null; // This is used to record the starting element of the scroll, so as to ensure that the scrollTop is set for the element during the replay operation without any deviation
        let scrollRecordList = [];
        let scrollElementSet = new Set(a); setScrollWatcher =function (ev) {
            mouseX = ev && ev.clientX || mouseX;
            mouseY = ev && ev.clientY || mouseY;
            scrollStartEl = document.elementFromPoint(mouseX, mouseY);
            let el = scrollStartEl;
            while (el) {
                if (scrollElementSet.has(el)) {
                    el = null;
                } else{ el.onscroll = throttle(recordScrollInfo); scrollElementSet.add(el); el = el.parentNode; }}}; recordScrollInfo =function (ev) {
            let el = scrollStartEl;
            // Simple scrolling can also cause changes to the mouse's dom. SetScrollWatcher is also required to end the scrolling
            setScrollWatcher();
            let scrollRecordInfo = {
                mouseX: mouseX,
                mouseY: mouseY,
                scrollTopList: []}while (el) {
                scrollRecordInfo.scrollTopList.push(el.scrollTop);
                el = el.parentNode;
            }
            scrollRecordList.push(scrollRecordInfo);
            console.log(scrollRecordList)
        }
        // Bind the mouse movement event
        document.onmousemove = throttle(setScrollWatcher);

        // Test the keyboard input event
        // Let's assume that continuous keyboard input is the same input,
        // All I need to do is save the input order, record the last element clicked, and change its value!! (innerHtml/innerText) (innerHtml/innerText) (innerHtml/innerText) To support rich text
        document.onkeydown = function (ev) {
            console.log(ev)
        }
        const keyTestEl = document.getElementsByClassName('inputTest') [0];
        keyTestEl.dispatchEvent(clickEvent)
        console.log(keyTestEl, focusEvent)
        keyTestEl.dispatchEvent(focusEvent)
        // const keyEvent = new KeyboardEvent('keypress',{'key':'a'})
        //keyTestEl.dispatchEvent(keyEvent)
        //keyTestEl.value = '11111'

        // Test whether radio click events can be distributed
        document.getElementsByClassName('bbb') [0].dispatchEvent(clickEvent)
        document.getElementsByClassName('aaa') [0].dispatchEvent(focusEvent)
    </script>
</body>

</html>
Copy the code

conclusion

With little stars in your eyes, life will be sparkling

Thank you for reading and have a happy life

Next article, continue the story!

The last of the last: interested also can see my previous article ha ~ can also add my wechat ha! WeChat: hancao97