@@ -64,6 +64,8 @@ export default class Interpreter extends EventEmitter {
6464 private concurrency : Concurrency ;
6565
6666 private stopper : Function | null = null ;
67+
68+ private isAborted : boolean = false ;
6769
6870 private log : typeof log ;
6971
@@ -114,6 +116,13 @@ export default class Interpreter extends EventEmitter {
114116 } )
115117 }
116118
119+ /**
120+ * Sets the abort flag to immediately stop all operations
121+ */
122+ public abort ( ) : void {
123+ this . isAborted = true ;
124+ }
125+
117126 private async applyAdBlocker ( page : Page ) : Promise < void > {
118127 if ( this . blocker ) {
119128 try {
@@ -372,6 +381,11 @@ export default class Interpreter extends EventEmitter {
372381 * @param steps Array of actions.
373382 */
374383 private async carryOutSteps ( page : Page , steps : What [ ] ) : Promise < void > {
384+ if ( this . isAborted ) {
385+ this . log ( 'Workflow aborted, stopping execution' , Level . WARN ) ;
386+ return ;
387+ }
388+
375389 /**
376390 * Defines overloaded (or added) methods/actions usable in the workflow.
377391 * If a method overloads any existing method of the Page class, it accepts the same set
@@ -433,6 +447,11 @@ export default class Interpreter extends EventEmitter {
433447 } ,
434448
435449 scrapeSchema : async ( schema : Record < string , { selector : string ; tag : string , attribute : string ; shadow : string } > ) => {
450+ if ( this . isAborted ) {
451+ this . log ( 'Workflow aborted, stopping scrapeSchema' , Level . WARN ) ;
452+ return ;
453+ }
454+
436455 if ( this . options . debugChannel ?. setActionType ) {
437456 this . options . debugChannel . setActionType ( 'scrapeSchema' ) ;
438457 }
@@ -468,6 +487,11 @@ export default class Interpreter extends EventEmitter {
468487 } ,
469488
470489 scrapeList : async ( config : { listSelector : string , fields : any , limit ?: number , pagination : any } ) => {
490+ if ( this . isAborted ) {
491+ this . log ( 'Workflow aborted, stopping scrapeList' , Level . WARN ) ;
492+ return ;
493+ }
494+
471495 if ( this . options . debugChannel ?. setActionType ) {
472496 this . options . debugChannel . setActionType ( 'scrapeList' ) ;
473497 }
@@ -622,6 +646,11 @@ export default class Interpreter extends EventEmitter {
622646 limit ?: number ,
623647 pagination : any
624648} ) {
649+ if ( this . isAborted ) {
650+ this . log ( 'Workflow aborted, stopping pagination' , Level . WARN ) ;
651+ return [ ] ;
652+ }
653+
625654 let allResults : Record < string , any > [ ] = [ ] ;
626655 let previousHeight = 0 ;
627656 let scrapedItems : Set < string > = new Set < string > ( ) ;
@@ -635,6 +664,12 @@ export default class Interpreter extends EventEmitter {
635664 } ;
636665
637666 const scrapeCurrentPage = async ( ) => {
667+ // Check abort flag before scraping current page
668+ if ( this . isAborted ) {
669+ debugLog ( "Workflow aborted, stopping scrapeCurrentPage" ) ;
670+ return ;
671+ }
672+
638673 const results = await page . evaluate ( ( cfg ) => window . scrapeList ( cfg ) , config ) ;
639674 const newResults = results . filter ( item => {
640675 const uniqueKey = JSON . stringify ( item ) ;
@@ -723,7 +758,12 @@ export default class Interpreter extends EventEmitter {
723758 let unchangedResultCounter = 0 ;
724759
725760 try {
726- while ( true ) {
761+ while ( true ) {
762+ if ( this . isAborted ) {
763+ this . log ( 'Workflow aborted during pagination loop' , Level . WARN ) ;
764+ return allResults ;
765+ }
766+
727767 switch ( config . pagination . type ) {
728768 case 'scrollDown' : {
729769 let previousResultCount = allResults . length ;
@@ -734,10 +774,22 @@ export default class Interpreter extends EventEmitter {
734774 return allResults ;
735775 }
736776
737- await page . evaluate ( ( ) => window . scrollTo ( 0 , document . body . scrollHeight ) ) ;
777+ await page . evaluate ( ( ) => {
778+ const scrollHeight = Math . max (
779+ document . body . scrollHeight ,
780+ document . documentElement . scrollHeight
781+ ) ;
782+
783+ window . scrollTo ( 0 , scrollHeight ) ;
784+ } ) ;
738785 await page . waitForTimeout ( 2000 ) ;
739786
740- const currentHeight = await page . evaluate ( ( ) => document . body . scrollHeight ) ;
787+ const currentHeight = await page . evaluate ( ( ) => {
788+ return Math . max (
789+ document . body . scrollHeight ,
790+ document . documentElement . scrollHeight
791+ ) ;
792+ } ) ;
741793 const currentResultCount = allResults . length ;
742794
743795 if ( currentResultCount === previousResultCount ) {
@@ -969,6 +1021,11 @@ export default class Interpreter extends EventEmitter {
9691021 // const MAX_NO_NEW_ITEMS = 2;
9701022
9711023 while ( true ) {
1024+ if ( this . isAborted ) {
1025+ this . log ( 'Workflow aborted during pagination loop' , Level . WARN ) ;
1026+ return allResults ;
1027+ }
1028+
9721029 // Find working button with retry mechanism
9731030 const { button : loadMoreButton , workingSelector, updatedSelectors } = await findWorkingButton ( availableSelectors ) ;
9741031
@@ -1024,10 +1081,22 @@ export default class Interpreter extends EventEmitter {
10241081
10251082 // Wait for content to load and check scroll height
10261083 await page . waitForTimeout ( 2000 ) ;
1027- await page . evaluate ( ( ) => window . scrollTo ( 0 , document . body . scrollHeight ) ) ;
1084+ await page . evaluate ( ( ) => {
1085+ const scrollHeight = Math . max (
1086+ document . body . scrollHeight ,
1087+ document . documentElement . scrollHeight
1088+ ) ;
1089+
1090+ window . scrollTo ( 0 , scrollHeight ) ;
1091+ } ) ;
10281092 await page . waitForTimeout ( 2000 ) ;
1029-
1030- const currentHeight = await page . evaluate ( ( ) => document . body . scrollHeight ) ;
1093+
1094+ const currentHeight = await page . evaluate ( ( ) => {
1095+ return Math . max (
1096+ document . body . scrollHeight ,
1097+ document . documentElement . scrollHeight
1098+ ) ;
1099+ } ) ;
10311100 const heightChanged = currentHeight !== previousHeight ;
10321101 previousHeight = currentHeight ;
10331102
@@ -1120,6 +1189,11 @@ export default class Interpreter extends EventEmitter {
11201189 }
11211190
11221191 private async runLoop ( p : Page , workflow : Workflow ) {
1192+ if ( this . isAborted ) {
1193+ this . log ( 'Workflow aborted in runLoop' , Level . WARN ) ;
1194+ return ;
1195+ }
1196+
11231197 let workflowCopy : Workflow = JSON . parse ( JSON . stringify ( workflow ) ) ;
11241198
11251199 workflowCopy = this . removeSpecialSelectors ( workflowCopy ) ;
@@ -1150,6 +1224,11 @@ export default class Interpreter extends EventEmitter {
11501224 const MAX_LOOP_ITERATIONS = 1000 ; // Circuit breaker
11511225
11521226 while ( true ) {
1227+ if ( this . isAborted ) {
1228+ this . log ( 'Workflow aborted during step execution' , Level . WARN ) ;
1229+ return ;
1230+ }
1231+
11531232 // Circuit breaker to prevent infinite loops
11541233 if ( ++ loopIterations > MAX_LOOP_ITERATIONS ) {
11551234 this . log ( 'Maximum loop iterations reached, terminating to prevent infinite loop' , Level . ERROR ) ;
@@ -1232,6 +1311,11 @@ export default class Interpreter extends EventEmitter {
12321311 }
12331312 lastAction = action ;
12341313
1314+ if ( this . isAborted ) {
1315+ this . log ( 'Workflow aborted before action execution' , Level . WARN ) ;
1316+ return ;
1317+ }
1318+
12351319 try {
12361320 console . log ( "Carrying out:" , action . what ) ;
12371321 await this . carryOutSteps ( p , action . what ) ;
0 commit comments