First off the code, which runs without error but isn't covering everything I need it to:
public async Task SaveData()
{
// MessageDialog restartMessage;
// ^^ This was made global and is defined in SafeToSave()
// Check if it's safe to save
await SafeToSave();
if (restartMessage != null)
{
CoreDispatcher cD = Window.Current.CoreWindow.Dispatcher;
await cD.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
await restartMessage.ShowAsync();
// This WILL invoke 'Application.Current.Exit()'
});
return;
// Issue is right around here...
}
// Other code...
}
The basic premise is that if it is not safe to save, a message dialog will tell the user that the app needs to be restarted and then perform 'Application.Current.Exit()' when they then tap the 'Okay' button.
My problem is that I need my code to stop executing when that restart is hit. Right now 'return' only ends this method, but after that it will continue to do other things in the method that called SaveData() until my restartMessage takes effect, and that's not good.
I considered putting 'Application.Current.Exit()' above the 'return' statement, but that will shut things down before my error message can be seen. Which is also not good.
The other solution was skipping the whole CoreDispatcher thing and just running ShowAsync() by itself, except that would trigger a different known error. Basically I have a few MessageDialogs that call the SaveData() method and opening a MessageDialog when another MessageDialog is already open is, again, not good.
That's about it: I need something to stop my code form executing without killing the whole application or preventing my error message from displaying. What can I do?
Short version, I used this guy's solution: MessageDialog ShowAsync throws accessdenied exception on second dialog
Since I can't control when the Dispatcher is going to occur, it's necessary to scrap it. Now I said I couldn't do that because it was preventing another bug, so I used the advice in that link to prevent that bug in a different way... so now I can ditch the Dispatcher and force my application to deal with my message dialog without it continuing along its multi-threaded path and doing things it shouldn't!
Code for my solution is moved into the SafeToSave() method. I did what the guy in that link did and then changed this:
CoreDispatcher cD = Window.Current.CoreWindow.Dispatcher;
await cD.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
await restartMessage.ShowAsync();
});
Into this:
await restartMessage.ShowAsync();
And unlike before I'm not getting an access error for calling ShowAsync() while another MessageDialog is already up, because I'm forcibly closing it first.
Related
I'm trying to automate a process with puppeteer. When adding a new feature that implied the usage of a new tab opened in a different window, I started having a Target closed error (stack below). I'm familiar with this error in other situations, but now I don't have a clue as to why this is happening. The version of puppeteer I'm using is 19.0.0.
This is the error stack:
Target closed
at node_modules/puppeteer-core/src/common/Page.ts:1599:26
at onceHandler (node_modules/puppeteer-core/src/common/EventEmitter.ts:130:7)
at node_modules/puppeteer-core/lib/cjs/third_party/mitt/index.js:3:232
at Array.map (<anonymous>)
at Object.emit (node_modules/puppeteer-core/lib/cjs/third_party/mitt/index.js:3:216)
at CDPSessionImpl.emit (node_modules/puppeteer-core/src/common/EventEmitter.ts:118:18)
at CDPSessionImpl._onClosed (node_modules/puppeteer-core/src/common/Connection.ts:457:10)
at Connection.onMessage (node_modules/puppeteer-core/src/common/Connection.ts:164:17)
at WebSocket.<anonymous> (node_modules/puppeteer-core/src/common/NodeWebSocketTransport.ts:50:24)
at WebSocket.onMessage (node_modules/puppeteer-core/node_modules/ws/lib/event-target.js:199:18)
When skipping the procedure that involves the usage of a second window, the error doesn't show.
This is my cleanup method, which is run when the process finished running:
public async destroy() {
let browserIsConnected: boolean = !!this._browser?.isConnected();
if (this._browser && browserIsConnected) {
for (let pg of await this._browser.pages()) {
this.logger.debug(`Closing page ${pg.url()}`);
await pg.close();
}
this.logger.debug(`Closing browser instance...`);
await this._browser?.close();
this.logger.log(`Closed browser connection`);
} else
this.logger.log(`Browser already destroyed`);
delete this._browser;
}
I tried omitting the page.close() calls but it didn't affect anything, and try/catching every library call in the method, but none throw. When running the code, the error is logged in parallel with this._browser?.close(), framed by the logs above and below it. However, the stack does not relate to the function call and I don't know how to catch it. Other than this, the process runs smoothly and the browser closes successfully, but this error is making my integration tests fail. Sorry about not sharing a reproducible case, but I couldn't reproduce it without disclosing my business logic.
My question is: why is this happening? is there any way to avoid it?
I eventually figured this out, and the source of this problem wasn't in the code described below but in the event handling logic during the process. While waiting the popup to show up, I was doing the following:
page.once('popup', async (newPage: Page) => {
// capture the information inside the page, with many awaits
});
What I didn't know was that mitt, puppeteer's underlying event handling library, doesn't support asynchronous event handlers, not awaiting my event properly. This was solved by resolving the promise from the handler and awaiting it further in the code:
let pagePromiseResolve: Function;
let pagePromise: Promise<Page> = new Promise(resolve => {
pagePromiseResolve = resolve;
});
page.once('popup', newPage => pagePromiseResolve(newPage));
let newPage = await pagePromise;
// capture the information inside the page, with many awaits
I'm leaving this here in case it helps somebody, either with this specific use-case or with awaiting events with a library alike mitt.
I'm new to cypress and have ran into an issue. I have my base URL set to the domain I want to test, the issue is when I want to test the ability to login on my base url site I need to verify the user on another site, once I click apply on site number 2 the page on my base url reloads and I would then be able to test the rest of the site.
When I try to visit site 2 from my test I get an error
cy.visit() failed because you are attempting to visit a URL that is of
a different origin.
The new URL is considered a different origin because the following
parts of the URL are different:
superdomain
You may only cy.visit() same-origin URLs within a single test.
I read this https://docs.cypress.io/guides/guides/web-security.html#Set-chromeWebSecurity-to-false I've tried setting "chromeWebSecurity": false in cypress.json but I still get the same issue (I'm running in chrome)
Is there something I am missing?
As a temporary but solid work around, I was able to find this script in one of the Cypress Git issue threads (I don't remember where I found it so I can't link back to it)
Add the below to your cypress commands file
Cypress.Commands.add('forceVisit', url => {
cy.window().then(win => {
return win.open(url, '_self');
});
});
and in your tests you can call
cy.forceVisit("www.google.com")
From version 9.6.0 of cypress, you can use cy.origin.
If you want to use it, you must first set the "experimentalSessionAndOrigin" record to true.
{
"experimentalSessionAndOrigin": true
}
And here's how to use it.
cy.origin('www.example.com', () => {
cy.visit('/')
})
cy.origin change the baseurl, so you can link to another external link via cy.visit('/').
You can stub the redirect from login site to base site, and assert the URL that was called.
Based on Cypress tips and tricks here is a custom command to do the stubbing.
The login page may be using one of several methods to redirect, so besides the replace(<new-url>) stub given in the tip I've added href = <new-url> and assign(<new-url>).
Stubbing command
Cypress.Commands.add('stubRedirect', () => {
cy.once('window:before:load', (win) => {
win.__location = { // set up the stub
replace: cy.stub().as('replace'),
assign: cy.stub().as('assign'),
href: null,
}
cy.stub(win.__location, 'href').set(cy.stub().as('href'))
})
cy.intercept('GET', '*.html', (req) => { // catch the page as it loads
req.continue(res => {
res.body = res.body
.replaceAll('window.location.replace', 'window.__location.replace')
.replaceAll('window.location.assign', 'window.__location.assign')
.replaceAll('window.location.href', 'window.__location.href')
})
}).as('index')
})
Test
it('checks that login page redirects to baseUrl', () => {
cy.stubRedirect()
cy.visit(<url-for-verifying-user>)
cy.wait('#index') // waiting for the window load
cy.('button').contains('Apply').click() // trigger the redirect
const alias = '#replace' // or '#assign' or '#href'
// depending on the method used to redirect
// if you don't know which, try each one
cy.get(alias)
.should('have.been.calledOnceWith', <base-url-expected-in-redirect>)
})
You can't!
But, maybe it will be possible soon. See Cypress ticket #944.
Meanwhile you can refer to my lighthearted comment in the same thread where I describe how I cope with the issue while Cypress devs are working on multi-domain support:
For everyone following this, I feel your pain! #944 (comment) really gives hope, so while we're patiently waiting, here's a workaround that I'm using to write multi-domain e2e cypress tests today. Yes, it is horrible, but I hope you will forgive me my sins. Here are the four easy steps:
Given that you can only have one cy.visit() per it, write multiple its.
Yes, your tests now depend on each other. Add cypress-fail-fast to make sure you don't even attempt to run other tests if something failed (your whole describe is a single test now, and it makes sense in this sick alternate reality).
It is very likely that you will need to pass data between your its. Remember, we're already on this crazy “wrong” path, so nothing can stop us naughty people. Just use cy.writeFile() to save your state (whatever you might need), and use cy.readFile() to restore it at the beginning of your next it.
Sue me.
All I care about at this point is that my system has tests. If cypress adds proper support for multiple domains, fantastic! I'll refactor my tests then. Until that happens, I'd have to live with horrible non-retriable tests. Better than not having proper e2e tests, right? Right?
You could set the window.location.href manually which triggers a page load, this works for me:
const url = 'http://localhost:8000';
cy.visit(url);
// second "visit"
cy.window().then(win => win.location.href = url);
You will also need to add "chromeWebSecurity": false to your cypress.json configuration.
Note: setting the window to navigate won't tell cypress to wait for the page load, you need to wait for the page to load yourself, or use timeout on get.
During the past week I have faced a nasty issue with my google apps script, which has been working just fine for several years already, with at least 500 users.
When I'm trying to get the login information of the current user this way:
var email_act = Session.getActiveUser().getEmail();
the server throws an exception:
Exception: You do not have permission to call Session.getActiveUser.
Required permissions: https://www.googleapis.com/auth/userinfo.email
at getUserInfo(UserFunctions:46:27)
In my case the effective user is different than active user, but I got the same error also when trying to call Session.getEffectiveUser().
This happens randomly, approx 30% of calls and so far my solution was simply to retry until it succeeds. Note that when I did the retries in the backend-code, it did not solve the problem, maybe because there's something wrong with the "session" itself. If it failed once, it failed also all the consequent calls. Therefore, I implemented the retry-loop in front-end code (assuming that each call to back-end is then considered as a new session) and that helped - after a few (worst case 5) retries it works. The Back-end has now a simple try-catch error handler and it returns status (-1) to FE in case of failure.
Any ideas what might be the root cause? My solution is way too uggly to live with...
Thanks in advance!
I just came across the same error today. For me, the Session.getActiveUser().getEmail() was in a scriplet on a dynamic webpage I create with a script using the function "include" that is defined in the code.gs like this:
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
It seems changing this to
function include(filename) {
return HtmlService.createTemplateFromFile(filename).evaluate().getContent();
}
has cleared things right up. I believe the error started occuring when I switched the code to the new V8 engine in GAS.
EDIT: oops, it worked the first time, but then failed after that. So I had to move the Session.getActiveUser().getEmail() inside code.gs
function getUserEmail() {
return Session.getActiveUser().getEmail()
}
and in the javascript on the webpage I had to use a callback :
google.script.run.withSuccessHandler(function (email) {
document.getElementById("yourIDhere").innerHTML = email;
}).getUserEmail();
Not very pretty at all, but it's working reliably now.
In Google apps script when using a client sided .HTML file you can call a server sided script using google.script.run.(Function name).
You can see the related documentation here: https://developers.google.com/apps-script/guides/html/reference/run
Now this script has been working with no problems over the first 6 months of its lifetime or so. I have not touched the program and I have not been notified or have located any newly deprecated code.
Over the course of the last couple months however, my users have been reporting that when they finish interacting with the HTML document, nothing happens when they close it and they have to repeat the entire process 3 or sometimes even 4 times before they will get it to go through.,
This means that when the user closes the client sided HTML window, the server sided function should be called to handle the remaining tasks but in some cases is not. This issue is completely random, and does not seem to be caused by anything specific.
I have taken some steps myself to attempt to solve the issue. I have wrapped the entirety of the code in try catch blocks, including the .HTML and .GS files. This means that if literally ANYTHING goes wrong in ANY script, I will be notified of it immediately. However, despite this being the case I am yet to receive any emails of it failing even though I watch it fail with my own eyes. I have added log commands before and after this function to see if it stops working all together or continues. In every case regardless of whether the function call is successful or not the log commands go through.
To me this can only mean that for some reason the function google.script.run is not working properly, and is failing to run the associated function, but is not returning an error message or stopping the script.
I am at an absolute loss since I have no error message, no reproducible steps, and no history of this being a problem before while suddenly starting to get worse and worse over time. I have checked Google's issue tracker to no results. If anyone else is using this function and is having problems I would love you to share your experiences here. If you have a solution please let me know as soon as possible. If I can't fix this issue I am going to have to use a new platform entirely.
Edit 10/2:
After looking further into this issue I have discovered a list of all executions on this project. I can see what functions were executed, when, and how long they took to execute. I can see that when the function that opens the HTML service is ran, the next function that should run does not always appear in the list. And when it doesn't, I can see that the user repeated their steps until it did run. This supports my theory that the function just isn't running when it should be after being called my script.run
Tl;dr: The affected computers are running so slowly that google.script.host.close would run before google.script.run.functionName() is able to be called and the information passed from the client to server, causing the function to never run but also not return an error. Adding Utilities.sleep(1000) fixes the issue.
I'm answering here in the situation that someone stumbles upon this thread in the future because they're having similar problems.
I was able to fix the issue by adding two lines of code between
google.script.run and google.script.host.close.
I added Google's Utilities.sleep(1000) to force the computer to wait one second between executing the function and closing the HTML window. I also added an HTML alert that shows that the function was called and didn't suffer from a runtime error.
I don't know exactly why this seems to have fixed the issue but I have a theory.
I have about 20 computers this spreadsheet runs on. Only about 6 of them were having the issue, and this wasn't brought to my attention until recently. As it turns out the 6 computers that were having the issue were the slowest computers of the bunch.
My theory is that the computers were so slow, and the internet bandwidth was fluctuating so much that the computer simply didn't have time to call google.script.run and pass off the information from the client sided HTML window that it simply got closed and cut off when google.script.host.close was run. This means that the function will not exist in the execution transcripts or history, nor will there be any sort of runtime error. All of those things were true in my situation. This also explains why I never had the issue on any of my own equipment in a testing environment since it didn't suffer from any slowdowns the other computers were having.
By adding both Utilities.sleep(1000) and the UI alert this forces the javascript to not continue to google.script.host.close until the user interacts with the UI alert (Which is just a confirmation window with an OK button) and afterwards waits a full second. This sacrifices a tiny bit of user friendly-ness for a more functional script. Since I have implemented this "fix" none of my users are reporting any issues and all of my execution history looks just fine.
Hopefully this helps any future passerbys.
In the comments you posted this function snippet:
Here is a basic copy of the script that utilizes google.script.run:
function onFailure(error) {
MailApp.sendEmail("sparkycbass#gmail.com", "Order book eror", "ERROR: " + error.message);
google.script.host.close();
}
function handleFormSubmit(formObject) {
google.script.run.withFailureHandler(onFailure).processForm(formObject)
google.script.host.close();
}
The problem here is that google.script.run is asynchronous - the call to your server-side function processForm is not guaranteed to be even initiated before the call to google.script.host.close() is made:
Client-side calls to server-side functions are asynchronous: after the browser requests that the server run the function doSomething(), the browser continues immediately to the next line of code without waiting for a response. This means that server function calls may not execute in the order you expect. If you make two function calls at the same time, there is no way to know which function will run first; the result may differ each time you load the page. In this situation, success handlers and failure handlers help control the flow of your code.
A proper pattern is to only call "destructive" commands - such as closing the host and therefore unloading all the relevant Apps Script instances - after the server has indicated the async operation completed. This is within the success handler of the google.script.run call:
.html
function onFailure(error) { // server function threw an unhandled exception
google.script.run.sendMeAnEmail("Order book error", "ERROR: " + error.message);
console.log(error);
document.getElementById("some element id").textContent = "There was an error processing that form. Perhaps try again?"
}
function onSuccess(serverFunctionOutput, userObj) {
// do stuff with `serverFunctionOutput` and `userObj`
// ...
google.script.host.close();
}
function handleFormSubmit(formObject) {
google.script.run
.withFailureHandler(onFailure)
.withSuccessHandler(onSuccess)
.processForm(formObject);
}
.gs
function processForm(formData) {
console.log({message: "Processing form data", input: formData});
// ...
}
function sendMeAnEmail(subject, message) {
console.log({message: "There was a boo-boo", email: {message: message, subject: subject}});
MailApp.sendEmail("some email", subject, message);
}
In the example here and in this issue comment it is suggested to use the useBeforeUnload function of history.
However, this function is gone. It seems to be removed without replacement in the current 4.x version:
Removed the "middleware" API (i.e. all "use" functions).
What's a good way to handle catch these "before unload" cases now in order to show a confirmation dialog?
Taking a look at the code for history's createBrowserHistory, it looks like the block function does what you need. From the docs:
// Register a simple prompt message that will be shown the
// user before they navigate away from the current page.
const unblock = history.block('Are you sure you want to leave this page?')
// Or use a function that returns the message when it's needed.
history.block((location, action) => {
// The location and action arguments indicate the location
// we're transitioning to and how we're getting there.
// A common use case is to prevent the user from leaving the
// page if there's a form they haven't submitted yet.
if (input.value !== '')
return 'Are you sure you want to leave this page?'
})
// To stop blocking transitions, call the function returned from block().
unblock()
https://github.com/mjackson/history#blocking-transitions