Small talk

Hi, everybody, it’s sunny and sunny in Shanghai today. The temperature of these two days is quite high, in the indoor environment, the army of short-sleeved has been increasingly rampant, out of control, and WEARING long Johns, I, in this force, slightly seem a little incompatible…

I haven’t updated this article in a long time. Decided a series of special things before the year, is now in an orderly way. Then, I recently had a little insight, that is, everything can not think too simple, or to plan the time of the project. And I this is a tragic lesson: is the original thought of a function is very simple, so put it off, did not take it seriously, until the fast delivery of the time node, only to find that it seems not simple, so crazy to add a few days of work… R.

background

What should I write today? First, a brief background: every time a project is developed and tested, it is necessary to go through whether the basic functions have been realized, to see how the quality of the proposed test is, whether some basic functions have not been done, whether the interface has not been adjusted, and whether the method has been realized? And so on.

So, on the above said these problems, if the conventional to do, or is the development of the development of their own point to see, or to the test of this piece, test a point to see.

So at the end of the day, it’s all about checking. Emmmm. So is there a simple and convenient way to replace the manual click test? I tossed and turned a few sleepless nights thinking I’d write a tool to do it all in an automated way. Hee hee, so said to do it.

Today, I will share with you a recent small tool: automatic smoke test plug-in.

conceived

So since you want to do an automated click thing, what do you need to do first? You need to retrieve each DOM element on the page to determine which events it is bound to.

Speaking of which, you probably know more or less, and you can see it in the developer tools that come with Chrome. It’s all there under EventListener. As shown in figure:

You want to see click events, or mouse events, everything, hee hee.

So this can only be included in the developer tools, but if you want to write a program, how to determine? Emmm, I think so. But aren’t there getEventListeners on this journey? Is it possible to use this to see if the corresponding element has an event?

So, I did a little demo experiment. The first thing that comes to mind is global detection:

As you can see, you can actually get the corresponding events, but it’s weird to see how there are only two click events on one page. Excuse me? Mind meditation disturb. Take leave.

But a little bit cynical, how could there be only two clicks, so I tried a new method:

The obvious thing to see is, hey, hey, that’s right. Global incomplete, it is good to do it individually. So this way you can figure out which elements have events. So is the next step just to iterate through the DOM elements of the page? Try it.

So he did it. The entire function is:

So you can get it. Simple ah. Code is here, so you can console and try it out.

Function getAllNodes (d) {/ / judgment parameter d = = = "*" && (d = document. GetElementsByTagName (" body ")); // Use arguments[1] to initialize an empty array! arguments[1] && (arguments[1] = []); for (var i = 0, l = d.length; i < l; I ++) {//nodeType === 1 push if (d[I].nodeType === 1) {if (d[I].classname.length > 0) {var className = d[i].className.replaceAll(" ", "."); arguments[1].push(classname); } // d[i].nameaa = d[i].className; If (d[I].haschildNodes ()) {if (d[I].haschildNodes ()) { arguments.callee(d[i].childNodes, arguments[1]); }} return [1] return arguments[1]; }; getAllNodes("*")Copy the code

Now that I’ve got all the elements on the page, can I just run them through the list using getEventListeners to see if there are events, flag them, tag them, and see if there are events on them?

So Console wrote a demo, and, well, it’s all right, traversal is fine. Hee hee, at the moment, very excited. Then it’s easy to just drop the iterated elements into the execution method one by one.

When everything was ready, I started the actual coding and found a serious problem. Almost vomited blood… The listeners on the process are not a function, is not defined. For a moment, I was stupid.

What the hell? How is that possible? I can do it on console, but I can’t do it if I write it to my program? Tease me? I was frustrated for a long time, so I started to look it up, but unfortunately, there were no blogs to explain it. So with doubts will die, must know why mentality, went to see the official document. GetEventListeners are available on console only for development and debugging!! So remember my friends, this is a big hole!!

Change the way of thinking

I’ve come a long way, all my work is wasted. This feels bad. But not willingly. I thought, well, if we’re going to do a smoke test, why do we have to know that the DOM element has a bound event?

Any element of the DOM itself is clickable. No incident, no reaction. On the contrary, why are getEventListeners important? When YOU think about it, it makes things seem easier.

$(“.xxxx”).click(); That’s it. Then walk. Cyclic events.

At the moment, I found a problem, the circulation is convenient, too fast, the event click too much, directly error… Insert a setTimeout in the loop. Execute one event per second. There are only so many events in the page.

tmp.forEach((ele, index) => { (function (ele, Function () {let currentName = $("." + ele)[0]. InnerText? $("." + ele)[0].innerText : "normal"; let eachlen = $("." + ele).length; if (eachlen > 1) { // $("." + ele)[1].click(); let obj = { ele: ele, len: eachlen, }; repeatclickevents.push(obj); let existeobj = { event: currentname && currentname.length < 10 ? currentname : ele, fatherevent: true, childlen: eachlen, }; loginfo.push(existeobj); } else { $("." + ele).click(); let obj = { event: currentname && currentname.length < 10 ? currentname : ele, fatherevent: false, childlen: 1, }; loginfo.push(obj); } // print log message console.log(' ${currentName && currentName. Length < 10? Currentname: ele} click event '); }, 1000 * (index + 1)); })(ele, index); });});});Copy the code

Well, good implementation of such a function. But there’s a little bit of a problem, is that a lot of the elements that you’re iterating over are child elements, so a lot of the Li tags, they’re all one element attributes, so when you execute a method, it doesn’t know which one to execute, and it gives you a collection of arrays.

So in that case, we’re going to do subdivision. Check if the element is a set of child elements before executing it. If so, kick it out and execute it later. If not, throw it directly into the method pool. Full marks for thinking. Hee hee.

By the way, the plugin also includes an analysis of the larger image resources on the page. There is no more description here.

function getImageSize() { const getEntries = window.performance.getEntries(); const imgR = getEntries.filter((ele) => ele.initiatorType === "img"); let newarr = []; imgR.forEach((ele) => { $("<img />") .attr("src", ele.name) .on("load", Var imgw = this.width; var imgw = this.width; var imgw = this.width; Var imgh = this.height; let splitnames = ele.name.split("."); let type = splitnames[splitnames.length - 1]; const newobj = { name: ele.name, type: type, transferSize: ele.transferSize, encodedBodySize: ele.encodedBodySize, decodedBodySize: ele.decodedBodySize, width: imgw, height: imgh, }; newarr.push(newobj); }); }); return newarr; }Copy the code

Ok, with that said, it’s time to record the monitoring after the execution event is complete.

monitoring

Since these smoke tests are performed in Chrome, the console will not let go of any errors, either from the interface or from the func itself. Hee hee, think of this, it is simple.

Use window.addEventListener in your application and record the error log during execution.

window.addEventListener(
    "error",
    function (e) {
      console.log("==============", e.message);
    },
    true
  );
  
Copy the code

But these are all displayed on the console, and if you want to generate the final report class file, it is definitely not possible. Since you want to generate a report, just drop it in the database. The second argument to window.addEventListener is a callback function, so the report to be executed needs to be implemented in the callback function.

Operation history

After monitoring, log the operation history again. This is actually easier, even if the execution of each step, log down, still fall into the library. I don’t need to talk about it here. I’ll provide the full code later.

The last set

The above describes the general implementation method, you should be able to understand. But the actual development, the problems solved, and the problems encountered are far more than that. There have been many twists and turns.

After all these procedural things are cleared up, it’s time to think about how to build such a tool. The natural thing for me was to write it in jquery. Emmm, for developers who are good at front-end development, jquery will be the most reflexive choice for those who need to interact with the DOM.

Good implementation method selection is good, so what form should be presented in the end? I thought about this for a moment, think plug-ins seem quite good, hee hee, simple and direct.

Effect of rendering

The final effect will be shown in the form of pictures

  1. Installing a plug-in

  1. Loading wait after execution

  1. Data log printing and reporting

  1. Platform View Report

The core code

Here only shows the plug-in core code code implementation, we can take a look at the reference. Welcome to exchange.

// 注意,必须设置了run_at=document_start 此段代码才会生效
document.addEventListener("DOMContentLoaded", function () {});

// 接收来自后台的消息
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  console.log("xixi,我进来啦~", request);
  sendResponse("resultResponse:" + JSON.stringify({ response: "response" }));
});
// get error message
// let errordatas = [];
function listeneerror(url) {
  // add listener
  window.addEventListener(
    "error",
    function (e) {
      console.log("==============", e.message);
      var obj = {
        type: "error",
        msg: e.message,
        url: url,
      };
      // errordatas.push(obj);
      // outputobj.errdata = errordatas;
      var data = JSON.stringify(obj);
      var xhr = new XMLHttpRequest();
      xhr.withCredentials = true;
      xhr.addEventListener("readystatechange", function () {
        if (this.readyState === 4) {
          // alert("数据已发送");
          console.log(this.responseText);
        }
      });
      xhr.open(
        "POST",
        "https://xxxxx:8080/smoketest"
      ); 
      xhr.setRequestHeader("Content-Type", "application/json");
      xhr.send(data);
    },
    true
  );
}
let loginfo = [];
// 主动发送消息给后台
// 要演示此功能,请打开控制台主动执行sendMessageToBackground()
function sendMessageToBackground(msg) {
  chrome.runtime.sendMessage({ info: msg }, function (response) {
    alert(response);
  });
}
let outputobj = {};

// outputobj.errarr = [];
// 监听长连接
chrome.runtime.onConnect.addListener(function (port) {
  console.log(port);
  if (port.name == "dosearch") {
    window.scrollTo({ left: 0, top: 3000, behavior: "smooth" });
    $("a").attr("target", "_blank");
    const currentinfo = getcurrentinfo();
    outputobj["currentinfo"] = currentinfo;
    var result = getAllNodes("*"); // get all class element
    var player = getAllplayerNodes("*");
    var outputresult;
    var imgresult = getImageSize(); // get imgs asserts
    outputobj["outputimgresult"] = imgresult;
    if (result.length > 0) {
      //
      var startindex, stopindex;
      if (result.indexOf("mini-upload.van-popover__reference")) {
        console.log("mini-upload.van-popover__reference");
        startindex = result.indexOf("mini-upload.van-popover__reference");
      } else {
        startindex = 0;
      }
      if (result.indexOf("international-footer")) {
        console.log("international-footer");
        stopindex = result.indexOf("international-footer");
      } else {
        stopindex = result.length;
      }
      result = result.slice(startindex, stopindex);
      // judge is exsit common
      if (result.indexOf("common")) {
        console.log("exsit common");
        var commonindex = result.indexOf("common");
        result = result.slice(0, commonindex);
      }

  if (player.length > 0) {
    console.log("存在player");
    outputresult = result.concat(player);
  } else {
    outputresult = result;
  }
  var tmp = new Array();
  // tmp = result; // test all click
  for (var i in outputresult) {
    //该元素在tmp内部不存在才允许追加
    if (tmp.indexOf(outputresult[i]) == -1) {
      tmp.push(outputresult[i]);
    }
  }

  let repeatclickevents = [];
  tmp.forEach((ele, index) => {
    (function (ele, index) {
      // 注意这里是形参
      setTimeout(function () {
        let currentname = $("." + ele)[0].innerText
          ? $("." + ele)[0].innerText
          : "normal";

        let eachlen = $("." + ele).length;
        if (eachlen > 1) {
          // $("." + ele)[1].click();
          let obj = {
            ele: ele,
            len: eachlen,
          };
          repeatclickevents.push(obj);
          let existeobj = {
            event:
              currentname && currentname.length < 10 ? currentname : ele,
            fatherevent: true,
            childlen: eachlen,
          };
          loginfo.push(existeobj);
        } else {
          $("." + ele).click();
          let obj = {
            event:
              currentname && currentname.length < 10 ? currentname : ele,
            fatherevent: false,
            childlen: 1,
          };
          loginfo.push(obj);
        }

        // print log message
        console.log(
          `执行 ${
            currentname && currentname.length < 10 ? currentname : ele
          } 点击事件`
        );
      }, 1000 * (index + 1)); // 还是每秒执行一次,不是累加的
    })(ele, index); // 注意这里是实参,这里把要用的参数传进去
  });
  // do more actions

  var startlen = tmp.length;
  var totalmeassagelen;
  (function (len) {
    setTimeout(function () {
      console.log("repeatclickevents: ", repeatclickevents);
      var lastlen = 0;
      repeatclickevents.forEach((elelenadd) => {
        lastlen += elelenadd.len;
      });
      totalmeassagelen = lastlen + len;
      // console.log("lastlen: ", lastlen);
      // console.log("len: ", len);
      // console.log("totalmeassagelen: ", totalmeassagelen);
      outputobj.totallength = totalmeassagelen;
      repeatclickevents.forEach((eles, indexs) => {
        // console.log(`${indexs}is indexs: `, indexs);
        (function (eles, indexs) {
          setTimeout(function () {
            indexs = indexs + 1;
            var totallen = 0;
            for (var j = 0; j < indexs; j++) {
              totallen += repeatclickevents[j].len;
            }
            // console.log("totallen: ", totallen);
            // let eachloginfo = [];
            for (let i = 0; i < eles.len; i++) {
              (function (i) {
                // 注意这里是形参
                setTimeout(function () {
                  // let currentnames = $("." + eles.ele)[i].innerText;
                  $("." + eles.ele)[i].click();
                  // print log message

                  console.log(`执行${eles.ele}第${i}个元素,点击事件`);
                }, 1000 * (i + totallen + 1)); // 还是每秒执行一次,不是累加的
              })(i); // 注意这里是实参,这里把要用的参数传进去
            }
          }, 1000 * (indexs + 1));
        })(eles, indexs); // 注意这里是实参,这里把要用的参数传进去
      });
      (function (len) {
        setTimeout(function () {
          // console.log("loginfo: ", loginfo);
          outputobj.loginfo = loginfo;
          // sendMessageToBackground(outputobj);
          // listeneerror();
          // sent data to api ==========
          var data = JSON.stringify(outputobj);
          var xhr = new XMLHttpRequest();
          xhr.withCredentials = true;
          xhr.addEventListener("readystatechange", function () {
            if (this.readyState === 4) {
              alert(
                "数据已发送,查看日志报告: http://xxxxx.co/#/Smoke"
              );
              console.log(this.responseText);
            }
          });
          xhr.open(
            "POST",
            "https://xxxxx:88080/smoketest"
          ); //
          xhr.setRequestHeader("Content-Type", "application/json");
          xhr.send(data);
        }, 1000 * (len + 5)); // len s 后执行
      })(totalmeassagelen); // 注意这里是实参,这里把要用的参数传进去
    }, 1000 * (len + 5)); // len s 后执行
  })(startlen); // 注意这里是实参,这里把要用的参数传进去
} else {
  sendMessageToBackground("抱歉,没有article 关键字~,请联系 IMT ");
}
// console.log("收集错误");
listeneerror(currentinfo.url);
//
  }
  // alert(JSON.stringify(result));
});

function getAllNodes(d) {
  //判断下参数
  d === "*" && (d = document.getElementsByTagName("body"));
  //用arguments[1] 初始化一个空数组
  !arguments[1] && (arguments[1] = []);
  for (var i = 0, l = d.length; i < l; i++) {
    //nodeType === 1 时 push
    if (d[i].nodeType === 1) {
      if (d[i].className.length > 0) {
        var classname = d[i].className.replaceAll(" ", ".");
        arguments[1].push(classname);
      }
      //   d[i].nameaa = d[i].className;
    }
    //有子节点 arguments[1]作为参数继续调用 arguments.callee 可以调用自身 匿名函数常用
    if (d[i].hasChildNodes()) {
      arguments.callee(d[i].childNodes, arguments[1]);
    }
  }
  //把arguments[1] return出来
  return arguments[1];
}

function getAllplayerNodes(d) {
  //判断下参数
  d === "*" &&
    (d = document.getElementsByClassName("bilibili-player-video-wrap"));
  //用arguments[1] 初始化一个空数组
  !arguments[1] && (arguments[1] = []);
  for (var i = 0, l = d.length; i < l; i++) {
    //nodeType === 1 时 push
    if (d[i].nodeType === 1) {
      if (d[i].className.length > 0) {
        var classname = d[i].className.replaceAll(" ", ".");
        arguments[1].push(classname);
      }
      //   d[i].nameaa = d[i].className;
    }
    //有子节点 arguments[1]作为参数继续调用 arguments.callee 可以调用自身 匿名函数常用
    if (d[i].hasChildNodes()) {
      arguments.callee(d[i].childNodes, arguments[1]);
    }
  }
  //把arguments[1] return出来
  return arguments[1];
}
// get img size
// transferSize 表示资源传输总大小,包含header
// encodedBodySize 表示压缩之后的body大小
// decodedBodySize 表示解压之后的body大小
function getImageSize() {
  const getEntries = window.performance.getEntries();
  const imgR = getEntries.filter((ele) => ele.initiatorType === "img");
  let newarr = [];
  imgR.forEach((ele) => {
    $("<img />")
      .attr("src", ele.name)
      .on("load", function () {
        //这里使用的jquery新建一个img对象进行添加attr属性,把src添加上去,然后进行载入事件
        var imgw = this.width; //这里的width和height就是图片实际的宽高了
        var imgh = this.height;
        let splitnames = ele.name.split(".");
        let type = splitnames[splitnames.length - 1];
        const newobj = {
          name: ele.name,
          type: type,
          transferSize: ele.transferSize,
          encodedBodySize: ele.encodedBodySize,
          decodedBodySize: ele.decodedBodySize,
          width: imgw,
          height: imgh,
        };
        newarr.push(newobj);
      });
  });
  return newarr;
}

// server do

function getcurrentinfo() {
  var name = document.title;
  var url = document.location.hostname + document.location.pathname;
  const obj = {
    url: url,
    name: name,
  };
  return obj;
}
Copy the code

Summary & Reflection

In fact, any tool is to be lazy. Emmm once and for all. But what we do now, we need to consider whether there is profit, whether there is value. Of course, there is no doubt about this, after all, there should be practical results.

On top of that, you should also be self-driven. If you code for business needs, then code will be less and less passionate and interested. So if you have an idea, just do it and enjoy it.

Hee hee, I hope this tool can bring you some ideas and help. If there is a need, welcome private I take a set of source code. If there is a code conversation, you can leave a message in the comments section.

The words of share

I pick up the pen, every word to consider and consider, but I do not know where to start, the letter is really want to write. Miss you is also true, the window of the rainbow into the moon, I did not fall a word, but in my heart. Read you for a long time.

That’s all for today’s sharing. Bye bye ~ ~