'แล้ว' หมายถึงอะไรจริงๆใน CasperJS


97

ฉันใช้ CasperJS เพื่อทำการคลิกแบบอัตโนมัติแบบฟอร์มที่เสร็จสมบูรณ์การแยกวิเคราะห์ข้อมูล ฯลฯ ผ่านเว็บไซต์

ดูเหมือนว่า Casper จะจัดเป็นรายการขั้นตอนที่ตั้งไว้ล่วงหน้าในรูปแบบของthenคำสั่ง (ดูตัวอย่างได้ที่นี่: http://casperjs.org/quickstart.html ) แต่ยังไม่ชัดเจนว่าอะไรเป็นสาเหตุให้คำสั่งถัดไปทำงานจริง

ตัวอย่างเช่นthenรอให้คำขอที่รอดำเนินการทั้งหมดเสร็จสมบูรณ์หรือไม่ ไม่injectJSนับเป็นคำขอที่รอดำเนินการ จะเกิดอะไรขึ้นถ้าฉันมีthenคำสั่งซ้อนกัน - ผูกมัดกับตอนท้ายของopenคำสั่ง?

casper.thenOpen('http://example.com/list', function(){
    casper.page.injectJs('/libs/jquery.js');
    casper.evaluate(function(){
        var id = jQuery("span:contains('"+itemName+"')").closest("tr").find("input:first").val();
        casper.open("http://example.com/show/"+id); //what if 'then' was added here?
    });
});

casper.then(function(){
    //parse the 'show' page
});

ฉันกำลังมองหาคำอธิบายทางเทคนิคว่าโฟลว์ทำงานอย่างไรใน CasperJS ปัญหาเฉพาะของฉันคือthenคำสั่งสุดท้ายของฉัน(ด้านบน) ทำงานก่อนcasper.openคำสั่งของฉันและฉันไม่รู้ว่าทำไม


1
ฉันยังคงมองหาคำอธิบายทั่วไปflowของ casperjs แต่ฉันพบว่าโดยพื้นฐานแล้วคุณไม่สามารถอ้างอิง casper จากภายในการevaluateโทรได้ (เช่นคุณไม่สามารถเปิด url, log, echo ฯลฯ ใหม่ได้) ดังนั้นในกรณีของฉันจึงถูกเรียกใช้การประเมิน แต่ไม่มีทางโต้ตอบกับโลกภายนอก
bendytree

1
ฉันสงสัยในสิ่งเดียวกัน แต่ขี้เกียจที่จะถาม คำถามที่ดี!
Nathan

4
evaluate()เป็นรหัสที่ทำงานใน "เบราว์เซอร์" ใน DOM ของเพจ phantomjs กำลังเรียกดู ดังนั้นจึงไม่มีcasper.openแต่อาจมี jQuery ดังนั้นตัวอย่างของคุณก็ไม่สมเหตุสมผล แต่ฉันก็ยังสงสัยว่าthen()จริงๆแล้วมันคืออะไร
Nathan

คำตอบ:


93

then()โดยทั่วไปจะเพิ่มขั้นตอนการนำทางใหม่ในสแต็ก ขั้นตอนคือฟังก์ชัน javascript ซึ่งสามารถทำสองสิ่งที่แตกต่างกัน:

  1. กำลังรอขั้นตอนก่อนหน้า - ถ้ามี - กำลังดำเนินการ
  2. รอให้ URL ที่ร้องขอและหน้าที่เกี่ยวข้องโหลด

ลองใช้สถานการณ์การนำทางง่ายๆ:

var casper = require('casper').create();

casper.start();

casper.then(function step1() {
    this.echo('this is step one');
});

casper.then(function step2() {
    this.echo('this is step two');
});

casper.thenOpen('http://google.com/', function step3() {
    this.echo('this is step 3 (google.com is loaded)');
});

คุณสามารถพิมพ์ขั้นตอนที่สร้างขึ้นทั้งหมดภายในสแต็กได้ดังนี้:

require('utils').dump(casper.steps.map(function(step) {
    return step.toString();
}));

ที่ให้:

$ casperjs test-steps.js
[
    "function step1() { this.echo('this is step one'); }",
    "function step2() { this.echo('this is step two'); }",
    "function _step() { this.open(location, settings); }",
    "function step3() { this.echo('this is step 3 (google.com is loaded)'); }"
]

สังเกต_step()ฟังก์ชั่นที่ CasperJS เพิ่มโดยอัตโนมัติเพื่อโหลด url ให้เรา; เมื่อโหลด url ขั้นตอนต่อไปที่มีอยู่ในสแต็กซึ่งก็คือstep3()- เรียกว่า

เมื่อคุณกำหนดขั้นตอนการนำทางของคุณแล้วให้run()ดำเนินการทีละขั้นตอนตามลำดับ:

casper.run();

เชิงอรรถ:สิ่งที่เรียกกลับ / ฟังคือการดำเนินการของรูปแบบสัญญา


ใน casperjs 1.0.0-RC1 "test-steps.js" กำลังแสดงคอลเล็กชันของ [object DOMWindow] แทนที่จะเป็นชุดของสตริงนิยามฟังก์ชัน
starlocke

คอลเล็กชัน [object DOMWindow] ยังคงเป็นผลลัพธ์ใน 1.0.0-RC4 ฉันสงสัยว่านิยามฟังก์ชันเหล่านั้นหายไป
ไหน

1
ตอนแรกฉันคิดว่า CasperJS กำลังทำเคล็ดลับใหม่ในการแปลงฟังก์ชันเป็น DOMWindows แต่ปัญหาคือ "return this.toString ()" vs "return step.toString ()" - ฉันส่งการแก้ไขสำหรับคำตอบ
starlocke

5
สิ่งที่เรียกว่า 'กอง' ไม่ใช่คิวจริงหรือ? ขั้นตอนต่างๆถูกดำเนินการตามลำดับหากเป็นสแต็กที่เราไม่คาดหวังขั้นตอนที่ 3 ขั้นตอนที่ 2 ขั้นตอนที่ 1
Reut Sharabani

1
ฉันคิดว่ามันต้องเป็นแบบนี้: คุณมีขั้นตอนซ้อนกัน คุณทำตามขั้นตอนและประเมินมัน คุณสร้างคิวว่าง ขั้นตอนใด ๆ ที่สร้างขึ้นเนื่องจากการประมวลผลของขั้นตอนปัจจุบันจะถูกใส่ไว้ในคิวนี้ เมื่อขั้นตอนการประเมินเสร็จสิ้นขั้นตอนที่สร้างขึ้นทั้งหมดในคิวจะถูกวางไว้ด้านบนสุดของสแต็ก แต่รักษาลำดับไว้ในคิว (เช่นเดียวกับการผลักลงสแต็กในลำดับย้อนกลับ)
ทำเครื่องหมาย

33

then() เพียงลงทะเบียนชุดของขั้นตอน

run() และตระกูลของฟังก์ชันนักวิ่งการเรียกกลับและผู้ฟังล้วนเป็นสิ่งที่ทำงานจริงในการดำเนินการในแต่ละขั้นตอน

เมื่อใดก็ตามที่เป็นขั้นตอนเสร็จสมบูรณ์ CasperJS จะตรวจสอบกับ 3 ธง: pendingWait, และloadInProgress navigationRequestedหากแฟล็กใด ๆ เหล่านั้นเป็นจริงไม่ต้องทำอะไรเลยไม่ได้ใช้งานจนกว่าจะถึงเวลาต่อมา ( setIntervalสไตล์) หากไม่มีแฟล็กเหล่านั้นเป็นจริงขั้นตอนต่อไปจะถูกดำเนินการ

ในขณะที่ CasperJS 1.0.0-RC4 มีข้อบกพร่องซึ่งภายใต้สถานการณ์ที่อิงตามเวลาวิธีการ "พยายามทำขั้นตอนต่อไป" จะถูกเรียกใช้ก่อนที่ CasperJS จะมีเวลาเพิ่มค่าสถานะloadInProgressหรือnavigationRequestedแฟล็กอย่างใดอย่างหนึ่ง วิธีแก้ปัญหาคือการเพิ่มแฟล็กหนึ่งในนั้นก่อนที่จะออกจากขั้นตอนใด ๆ ที่คาดว่าแฟล็กเหล่านั้นจะถูกยกขึ้น (เช่นยกแฟล็กก่อนหรือหลังขอ a casper.click()) อาจจะเป็นเช่นนั้น:

(หมายเหตุ: นี่เป็นเพียงตัวอย่างเช่น psuedocode มากกว่าแบบฟอร์ม CasperJS ที่เหมาะสม ... )

step_one = function(){
    casper.click(/* something */);
    do_whatever_you_want()
    casper.click(/* something else */); // Click something else, why not?
    more_magic_that_you_like()
    here_be_dragons()
    // Raise a flag before exiting this "step"
    profit()
}

การห่อขึ้นการแก้ปัญหาที่เป็นบรรทัดเดียวของรหัสผมแนะนำblockStep()ใน GitHub นี้คำขอดึงขยายclick()และเป็นวิธีการที่จะช่วยรับประกันว่าเราจะได้รับพฤติกรรมที่คาดหวังเมื่อใช้clickLabel() then()ตรวจสอบคำขอข้อมูลเพิ่มเติมรูปแบบการใช้งานและไฟล์ทดสอบขั้นต่ำ


1
ข้อมูลเชิงลึกและข้อเสนอแนะที่เป็นประโยชน์และยอดเยี่ยมblockStepIMHO
Brian M. Hunt

เรายังคงพูดคุยเกี่ยวกับโซลูชัน "คำตอบสุดท้าย" ... ฉันหวังว่าเมื่อฉันใช้แง่มุม "ค่าเริ่มต้นทั่วโลก" CasperJS จะดึง
starlocke

1
ใช่แล้วจับตาดูมัน :)
starlocke

เรามีวิธีแก้ปัญหานี้หรือไม่? ถ้าใช่มันคืออะไร?
Surender Singh Malik

ขอบคุณมากที่อธิบายเรื่องนี้ พฤติกรรมนี้ฆ่าฉันมานานกว่าหนึ่งปีแล้วเนื่องจากการทดสอบการทำงานของ Casper ของฉันสำหรับแอปพลิเคชัน Ajax-heavy ล้มเหลวแบบสุ่มตลอดเวลา
brettjonesdev

0

ตามเอกสาร CasperJS :

then()

ลายเซ็น: then(Function then)

วิธีนี้เป็นวิธีมาตรฐานในการเพิ่มขั้นตอนการนำทางใหม่ไปยังสแต็กโดยจัดเตรียมฟังก์ชันง่ายๆ:

casper.start('http://google.fr/');

casper.then(function() {
  this.echo('I\'m in your google.');
});

casper.then(function() {
  this.echo('Now, let me write something');
});

casper.then(function() {
  this.echo('Oh well.');
});

casper.run();

คุณสามารถเพิ่มขั้นตอนได้มากเท่าที่คุณต้องการ โปรดสังเกตว่าCasperอินสแตนซ์ปัจจุบันผูกthisคีย์เวิร์ดให้คุณโดยอัตโนมัติภายในฟังก์ชันขั้นตอน

ในการรันขั้นตอนทั้งหมดที่คุณกำหนดให้เรียกใช้run()method และ voila

หมายเหตุ:คุณต้องstart()ใช้อินสแตนซ์ casper จึงจะใช้then()วิธีนี้ได้

คำเตือน:ฟังก์ชันขั้นตอนที่เพิ่มเข้ามาจะได้then()รับการประมวลผลในสองกรณีที่แตกต่างกัน:

  1. เมื่อฟังก์ชันขั้นตอนก่อนหน้านี้ถูกเรียกใช้งาน
  2. เมื่อมีการร้องขอ HTTP หลักก่อนหน้านี้ได้รับการดำเนินการและหน้าโหลด ;

หมายเหตุว่าไม่มีความหมายเดียวของหน้าโหลด ; เมื่อเหตุการณ์ DOMReady ถูกทริกเกอร์หรือไม่ "คำขอทั้งหมดกำลังดำเนินการเสร็จสิ้น" หรือไม่? "ตรรกะของแอปพลิเคชันทั้งหมดกำลังดำเนินการอยู่" หรือไม่? หรือ "แสดงองค์ประกอบทั้งหมด"? คำตอบขึ้นอยู่กับบริบทเสมอ ด้วยเหตุนี้คุณจึงได้รับการสนับสนุนให้ใช้waitFor()วิธีการของครอบครัวเพื่อควบคุมสิ่งที่คุณคาดหวังอย่างชัดเจน

เคล็ดลับทั่วไปคือการใช้waitForSelector():

casper.start('http://my.website.com/');

casper.waitForSelector('#plop', function() {
  this.echo('I\'m sure #plop is available in the DOM');
});

casper.run();

เบื้องหลังซอร์สโค้ดสำหรับCasper.prototype.thenแสดงอยู่ด้านล่าง:

/**
 * Schedules the next step in the navigation process.
 *
 * @param  function  step  A function to be called as a step
 * @return Casper
 */
Casper.prototype.then = function then(step) {
    "use strict";
    this.checkStarted();
    if (!utils.isFunction(step)) {
        throw new CasperError("You can only define a step as a function");
    }
    // check if casper is running
    if (this.checker === null) {
        // append step to the end of the queue
        step.level = 0;
        this.steps.push(step);
    } else {
        // insert substep a level deeper
        try {
            step.level = this.steps[this.step - 1].level + 1;
        } catch (e) {
            step.level = 0;
        }
        var insertIndex = this.step;
        while (this.steps[insertIndex] && step.level === this.steps[insertIndex].level) {
            insertIndex++;
        }
        this.steps.splice(insertIndex, 0, step);
    }
    this.emit('step.added', step);
    return this;
};

คำอธิบาย:

กล่าวอีกนัยหนึ่งคือthen()กำหนดเวลาขั้นตอนต่อไปในกระบวนการนำทาง

เมื่อthen()ถูกเรียกมันจะถูกส่งผ่านฟังก์ชันเป็นพารามิเตอร์ซึ่งจะเรียกเป็นขั้นตอน

ตรวจสอบว่าอินสแตนซ์เริ่มต้นแล้วหรือไม่และหากยังไม่เริ่มแสดงข้อผิดพลาดต่อไปนี้

CasperError: Casper is not started, can't execute `then()`.

จากนั้นตรวจสอบว่าpageวัตถุนั้นอยู่nullหรือไม่

ถ้าเงื่อนไขเป็นจริง Casper จะสร้างpageวัตถุใหม่

หลังจากนั้นthen()ตรวจสอบความถูกต้องของstepพารามิเตอร์เพื่อตรวจสอบว่าไม่ใช่ฟังก์ชันหรือไม่

หากพารามิเตอร์ไม่ใช่ฟังก์ชันจะแสดงข้อผิดพลาดต่อไปนี้:

CasperError: You can only define a step as a function

จากนั้นฟังก์ชันจะตรวจสอบว่า Casper กำลังทำงานอยู่หรือไม่

หาก Casper ไม่ทำงานให้ต่อthen()ท้ายขั้นตอนต่อท้ายคิว

มิฉะนั้นหาก Casper กำลังทำงานอยู่มันจะแทรกระดับย่อยที่ลึกกว่าขั้นตอนก่อนหน้า

สุดท้ายthen()ฟังก์ชันจะสรุปโดยการเปล่งstep.addedเหตุการณ์และส่งคืนวัตถุ Casper

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.