Login with Scriptable

Ok, I understand. I have removed the line await v.waitForLoad(); and now it works. But I got a new error. I tried to get the variable from the js (I mean the usd, it is only a number). I think it has to do with the return usd; right?

I tried to understand your text about the completion and so, but I don’t! Can you show me an example? How it is possible to get the returned variable after the v.evaluateJavaScript(js);?

I got the error message from one of your edits and you are right that it has to do with the return keyword.
My bad, I thought return usd; works, but it seems that only usd; works. Please replace return usd; with usd;. I hope that it works now!

You don’t need to run your javascript asynchronous in the WebView so you don’t need the completion function.

An example for that would be

let v = new WebView();
let result = await v.evaluateJavaScript(`
setTimeout(() => {
  completion("slow hi from WebView!");
}, 1000);
`, true);
console.log(result);

Because the script we evaluate in the WebView doesn’t return immediately a value due to the setTimeout, we need to tell Scriptable that it returns it later on (called “asynchronous”). This is done with the second argument to evaluateJavaScript set to true.

Hey, I tried only usd; but I also get the error that the variable usd is not defined!

You tried it like that?

js = `
let usd = document.getElementsByClassName('royalities')[0]
    .getElementsByTagName('currency-summary-sold-USD')[0]
    .innerText;
usd;`;
const result = await v.evaluateJavaScript(js);

That should work. At least it does for me (a similar script).

You could also use

js = `
document.getElementsByClassName('royalities')[0]
    .getElementsByTagName('currency-summary-sold-USD')[0]
    .innerText;
`;
const result = await v.evaluateJavaScript(js);

Hey there, what I have to use than? I mean after the const? I don’t now how to get the usd do show?


js = `let usd = document.getElementsByTagName('currency-summary-sold-USD').innerText;
usd;`;

const result = await v.evaluateJavaScript(js);

// use result in the widget
if (usd != null) {
    let tx2 = widget.addText(result[usd])
      tx2.centerAlignText()
      tx2.font = vlFnt
  }

If you want usd, you should not assign to result. And vice versa: if you assign to result, you can’t expect usd to have a value.
I suggest to read and learn about JavaScript before getting to the hearier aspects like evaluating strings in browser context.

I try to learn but therefore your help is really good! Can you give me an example how to access the usd? I changed it to the following and get no error but also no output:


js = `let usd = document.getElementsByTagName('currency-summary-sold-USD').innerText;
`;

const usd = await v.evaluateJavaScript(js);

// use result in the widget
if (usd != null) {
    let tx2 = widget.addText(usd)
      tx2.centerAlignText()
      tx2.font = vlFnt
      tx2.textColor = Color.red()
  } else {
    let tx2 = widget.addText("Fehler")
      tx2.centerAlignText()
      tx2.font = vlFnt
      tx2.textColor = Color.red()
}

From your code, one cannot see what “no output” could mean. Is the widget at least visible? And what does console.log(usd) tell you?
Frankly, you should start with something simple first. Don’t try to fly a 747 before you had some time in a flight simulator.

Hey, yes the widget is visible and I got the headline and the red „Fehler“ what means that usd is null!

Your code show nothing when I insert it to the code!

So “no output” actually means that the variable is null. That would have been a useful hint :wink:
My code actually logs the value of usd to the floating subwindow at the bottom of Scriptable. There should be a little rectangle with “1” inside to the left of the arrow.
You might actually want to think about what you did to the script the last time you changed it. The only statement executed in the browser is now let usd = .... What do you expect to be usd outside of this script executed in the browser? If you do not pass it to the outside?

This script works, but as @chrillek said you are mixing the contexts of browser and Scriptable.

The code that you run in the browser with evaluateJavaScript doesn’t know anything from Scriptable and vice versa.
So when you declare a variable usd in the browser context, it is only available there. The same is true for the Scriptable context.

As explained in previous posts there are ways to transfer data though.
To get data from Scriptable to the browser context, you can embed it in the string that contains your script, but you have to do it in such a way that the script is still valid. See my previous post

Now to get back data from the browser context to Scriptable, you have to “give it back” in the browser script with

let usd = ...
usd;

The value of the last statement in the browser script (in this case usd;) determines the value you get back from the function call v.evaluateJavaScript(...).

So when you write

let js = `let example = "example string";
example;
`;
let result = await v.evaluateJavaScript(js);

console.log(example) // logs: undefined
console.log(result) // logs: "example string"

example doesn’t exist in the Scriptable context and is therefore undefined, but we’ve saved the return value from v.evaluateJavaScript(...); into result and because of that result contains the string example string.

I’ve said that your script (the post I answer) works. The only problem is that you save the result from the browser script in the variable result, but then check the variable usd if it contains your data.
To fix this you can either

  • change the variable result on line const result = await v.evaluateJavascript(... to usd or
  • change every occurrence of the variable usd after that line to result.
    Whatever you choose as variable name (result or usd) on the mentioned line is then everything you need. So to add it to a widget, you’d write widget.addText(result) in case you chose result.

Ok I understand. I changed it and it works, I got no error for that. The problem is, when I see at the html source code of the site i got the following elements:


<div _ngcontent-c3="" class="col-6 count" id="tier-count"> 500 </div>

<h4 _ngcontent-c6="" class="royalties" id="currency-summary-sold-USD">...</h4>

<h4 _ngcontent-c6="" class="royalties" id="currency-summary-royalties-USD">... USD</h4>

<h4 _ngcontent-c6="" class="royalties" id="currency-summary-sold-GBP">...</h4>

<h4 _ngcontent-c6="" class="royalties" id="currency-summary-royalties-GBP">... GBP</h4>

<h4 _ngcontent-c6="" class="royalties" id="currency-summary-sold-EUR">...</h4>

<h4 _ngcontent-c6="" class="royalties" id="currency-summary-royalties-EUR">... EUR</h4>

<h4 _ngcontent-c6="" class="royalties" id="currency-summary-sold-JPY">...</h4>

<h4 _ngcontent-c6="" class="royalties" id="currency-summary-royalties-JPY">... JPY</h4>

When I try the following code which should be right, I got the result the result = null what means that the element wasn’t found, correct?


let js1 = `
let tier = document.getElementById('tier-count')innerText;

tier;
`;

const result = await v.evaluateJavaScript(js1);

I don’t understand where the fault is!

Glad to hear that it works now!

I think you just forgot a dot before innerText. I’ve marked it with a comment:

let js1 = `
let tier = document.getElementById('tier-count')innerText;
//                                             ^

tier;
`;

const result = await v.evaluateJavaScript(js1);

Hey, no i just forget the dot here, in the code it there. And I don’t understand why I don’t get the fields!

Here’s the error I got:

Error: Failed evaluating JavaScript with error on line 2: TypeError: null is not an object (evaluating ‘document.getElementById(‘tier-count’).innerText’)

I think the code doesn’t find the element or am I wrong?

If it doesn’t find the element, then it isn’t there. This could happen if the page hasn’t loaded fully yet. You can try to wait a little bit and then check if it is there:


let js1 = `
let tries = 0;
// check every 500 ms if the element is there
let intervalId = setInterval(check, 500);
// run it also instantly
check();

function check() {
  let tier = document.getElementById('tier-count');

  if (tier) {
    // we've found it => return its value
   done(tier.innerText);
  } else {
    tries++;
    if (tries >= 5) {
      // 2 seconds have passed => return nothing
      done(null);
    }
  }
}
function done(result) {
  // clear the interval so it doesn't check anymore
  clearInterval(intervalId);
  // return the result
  completion(result);
}
`;

// run the script asynchronous
const result = await v.evaluateJavaScript(js1, true);

This time we need to run the script asynchronous (also called async) because we wait inside the browser context and return a value later on with completion(result).

This is only a guess on my side, I have no idea if it will work this way…

Hey, yeah that worked :blush: Great! What way I have to use when I want to get the other fields I posted above too? Can I insert the fields in the code of you too? Because I don’t know how to access the fields later in the result if it’s not only one field!

You can of course add the other fields too, but you have to somehow check if these exist too. One way would be in the browser script (replace the check function with this code)

let fields = ['tier-count']; // add here all your fields
let tries = 0;
// check every 500 ms if the element is there
let intervalId = setInterval(check, 500);
// run it also instantly
check();

function check() {
  // search for all elements
  let elements = fields.map(f => document.getElementById(f));

  if (elements.every(e => e)) {
    // we've found all => return all values
   done(JSON.stringify(elements.map(e => e.innerText)));
  } else {
    tries++;
    if (tries >= 5) {
      // 2 seconds have passed => return nothing
      done(null);
    }
  }
}
// insert done function

and then in Scriptable

let js1 = `...`;
let result = await v.evaluateJavaScript(js1, true);
if (result) {
  result = JSON.parse(result);
  // use result here
  // it is an array with the same order as you've defined the `fields` array in the browser script
}

Hello,
I got a problem with this login methode. Maybe you can help me.

This is my code:

const v = new WebView();
await v.loadURL('https://login.consumer.shell.com/login');

await v.waitForLoad();

// v.present()

var Js =`o = document.getElementById('signInEmailAddress');
o.focus();
o.value = "test@testmail.com";
o.blur()`


const result = await v.evaluateJavaScript(Js);
await v.present()
console.log(result);

With this code i got this result:

When i delete “o.blur()”
I see the email in the email field but when i touch the screen the email will be deleted.

Have you tried it without o.focus()?

If this also doesn’t work then I suspect that there is a script running that logs the key events and sets the input field to the logged value when the input is blurred. If this is the case, you could try it by sending the correct keydown and keyup events to the input while it is focused.

2 Likes

Yes i tried it and this looks so:

and the result is the same.

Thanks for your answer. I will look an try it with key events.