import { Answerable , AnswersQuestions , CollectsArtifacts , Interaction , LogicError , Question , UsesAbilities } from '@serenity-js/core' ; import { formatted } from '@serenity-js/core/lib/io' ; import { Name , TextData } from '@serenity-js/core/lib/model' ; import { BrowseTheWeb } from '../abilities' ; desc Instructs the  {@link @serenity-js/core/lib/screenplay/actor~Actor} to
 *  execute a script within the context of the current browser window.
 *
 *  Please see the tests below for usage examples.
 *
 *  see LastScriptExecution . result export class ExecuteScript { desc Instantiates a version of this  {@link @serenity-js/core/lib/screenplay~Interaction} *  configured to load a script from `sourceUrl`.
     *
     *  param { string } sourceUrl returns { serenity-js static from ( sourceUrl ) { return new ExecuteScriptFromUrl ( sourceUrl ) ; } desc Schedules a command to execute asynchronous JavaScript in the context of the currently selected frame or window.
     *  The script fragment will be executed as the body of an anonymous function.
     *  If the script is provided as a function object, that function will be converted to a string for injection
     *  into the target window.
     *
     *  Any arguments provided in addition to the script will be included as script arguments and may be referenced
     *  using the `arguments` object. Arguments may be a `boolean`, `number`, `string`
     *  or `Target` (`Question<ElementFinder>`).
     *  Arrays and objects may also be used as script arguments as long as each item adheres
     *  to the types previously mentioned.
     *
     *  Unlike executing synchronous JavaScript with  ExecuteScript ,
     *  scripts executed with this function must explicitly signal they are finished by invoking the provided callback.
     *
     *  This callback will always be injected into the executed function as the last argument,
     *  and thus may be referenced with `arguments[arguments.length - 1]`.
     *
     *  If the script invokes the `callback` with a return value, this will be made available
     *  via the  LastScriptExecution . result .
     *
     *  **Please note** that in order to signal an error in the `script` you need to throw an  Error *  instead of passing it to the callback function.
     *
     *  example example example param { string | Function } script returns { ExecuteScriptWithArguments } see LastScriptExecution . result static async ( script ) { return new ExecuteAsynchronousScript ( script ) ; } desc Instructs the  {@link @serenity-js/core/lib/screenplay/actor~Actor} to
     *  execute a synchronous script in the context of the currently selected frame or window.
     *
     *  If the script returns a value, it will be made available via  LastScriptExecution . result .
     *
     *  example param { string | Function } script returns { ExecuteScriptWithArguments } see LastScriptExecution . result static sync ( script ) { return new ExecuteSynchronousScript ( script ) ; } } desc Allows for a script to be executed to be parametrised.
 *
 *  **Please note** that the arguments can be both synchronous and asynchronous  {@link @serenity-js/core/lib/screenplay~Question} s
 *  as well as regular static values.
 *
 *  abstract see ExecuteScript extends serenity-js export abstract class ExecuteScriptWithArguments extends Interaction { param { string | Function } script param { Array < Answerable < any > > } args constructor ( protected readonly script , protected readonly args = [ ] ) { super ( ) ; } desc Instantiates an  {@link @serenity-js/core/lib/screenplay~Interaction} *  to  Enter .
     *
     *  param { ... Array < Answerable < any > > } args returns { serenity-js public abstract withArguments ( ... args ) ; desc Makes the provided  {@link @serenity-js/core/lib/screenplay/actor~Actor} *  perform this  {@link @serenity-js/core/lib/screenplay~Interaction} .
     *
     *  param { UsesAbilities & AnswersQuestions } actor An  {@link @serenity-js/core/lib/screenplay/actor~Actor} to perform this  {@link @serenity-js/core/lib/screenplay~Interaction} returns { PromiseLike < void > } see {@link @serenity-js/core/lib/screenplay/actor~Actor} see {@link @serenity-js/core/lib/screenplay/actor~UsesAbilities} see {@link @serenity-js/core/lib/screenplay/actor~AnswersQuestions} performAs ( actor ) { return this . answerAll ( this . args ) . as ( actor ) . then ( args => this . executeAs ( actor , args ) ) . then ( ( ) => actor . collect ( TextData . fromJSON ( { contentType : 'text/javascript;charset=UTF-8' , data : this . script . toString ( ) , } ) , new Name ( 'Script source' ) ) ) ; } protected abstract executeAs ( actor , args ) ; private param { Array < Answerable < any > > } args private answerAll ( args ) { return { as : ( actor ) : Promise < any [ ] > => Promise . all ( args . map ( arg => { const maybeElementFinder = Question . isAQuestion ( arg ) ? arg . answeredBy ( actor ) : arg ; const maybePromise = maybeElementFinder . getWebElement ? maybeElementFinder . getWebElement ( ) : maybeElementFinder ; return actor . answer ( maybePromise ) ; } ) ) , } ; } } package class ExecuteAsynchronousScript extends ExecuteScriptWithArguments { withArguments ( ... args ) { return new ExecuteAsynchronousScript ( this . script , args ) ; } protected executeAs ( actor , args ) { return BrowseTheWeb . as ( actor ) . executeAsyncScript ( this . script , ... args ) ; } toString ( ) { return this . args . length > 0 ? formatted `#actor executes an asynchronous script with arguments: ${ this . args }` : `#actor executes an asynchronous script` ; } } package class ExecuteScriptFromUrl extends Interaction { constructor ( private readonly sourceUrl ) { super ( ) ; } desc Makes the provided  {@link @serenity-js/core/lib/screenplay/actor~Actor} *  perform this  {@link @serenity-js/core/lib/screenplay~Interaction} .
     *
     *  param { UsesAbilities & AnswersQuestions } actor returns { Promise < void > } see {@link @serenity-js/core/lib/screenplay/actor~Actor} see {@link @serenity-js/core/lib/screenplay/actor~UsesAbilities} see {@link @serenity-js/core/lib/screenplay/actor~AnswersQuestions} performAs ( actor ) { return BrowseTheWeb . as ( actor ) . executeAsyncScript ( `   var src = arguments[0];
                    var callback = arguments[arguments.length - 1];
        
                    var alreadyLoadedScripts = Array.prototype.slice
                        .call(document.getElementsByTagName('script'))
                        .map(script => script.src);
        
                    if (!! ~ alreadyLoadedScripts.indexOf(src)) {
                        return callback("Script from " + src + " has already been loaded");
                    }
        
                    var script = document.createElement('script');
                    script.onload = function () {
                        return callback();
                    };
                    script.onerror = function () {
                        return callback("Couldn't load script from " + src);
                    };
        
                    script.src = src;
                    script.async = true;
                    document.head.appendChild(script);
                ` , this . sourceUrl ) . then ( errorMessage => { if ( errorMessage ) { throw new LogicError ( errorMessage ) ; } } ) ; } desc Generates a description to be used when reporting this  {@link @serenity-js/core/lib/screenplay~Activity} .
     *
     *  returns { string } toString ( ) { return `#actor executes a script from ${ this . sourceUrl }` ; } } package class ExecuteSynchronousScript extends ExecuteScriptWithArguments { withArguments ( ... args ) { return new ExecuteSynchronousScript ( this . script , args ) ; } protected executeAs ( actor , args ) { return BrowseTheWeb . as ( actor ) . executeScript ( this . toString ( ) , this . script , ... args ) ; } toString ( ) { return this . args . length > 0 ? formatted `#actor executes a synchronous script with arguments: ${ this . args }` : `#actor executes a synchronous script` ; } }