emailgroovyjakarta-mailgmail-apikatalon-studio

How to retrieve link from email message using the Gmail plugin/GMail API for Java/Jakarta Mail API/...?


I am using Katalon Studio, and need to retrieve some sign-up link from test email inbox. I found some API/service to access the test email inbox, can get the message I need from it, and it's a string of what looks like HTML.

I don't care about the HTML, I just want to "click on" the link in that email message!

How do I do that!?


Solution

  • Assuming you successfully have the message string, here's how you can retrieve the link from it, assuming that your email message retrival method call returns HTML string.

    To save you some clicking:

    import javax.xml.parsers.DocumentBuilderFactory
    import javax.xml.xpath.XPathFactory
    
    import org.w3c.dom.Element
    
    // feel free to make this your own :)
    public final class EmailUtils {
    /**
         * **NOTE**: forked from https://stackoverflow.com/a/2269464/2027839 , and then refactored
         * 
         * Processes HTML, using XPath
         * 
         * @param html
         * @param xpath
         * @return the result 
         */
        public static String ProcessHTML(String html, String xpath) {
    
            final String properHTML = this.ToProperHTML(html);
    
            final Element document = DocumentBuilderFactory.newInstance()
                    .newDocumentBuilder()
                    .parse(new ByteArrayInputStream( properHTML.bytes ))
                    .documentElement;
            return XPathFactory.newInstance()
                    .newXPath()
                    .evaluate( xpath, document );
        }
    
        private static String ToProperHTML(String html) {
            // SOURCE: https://stackoverflow.com/a/19125599/2027839
            String properHTML = html.replaceAll( "(&(?!amp;))", "&" );
    
            if (properHTML.contains('<!DOCTYPE html'))
                return properHTML;
    
    
            return """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html>
        <head></head>
        <body>
            ${properHTML}
        </body>
    </html>
    """;
        }
    }
    
    

    From there, you'll have to log out your HTML message string (or put debug breakpoint around your method call and extract it from debugger), pretty print it, and from there, you can use your web-testing skills to create some xpath selector string for the actual link.

    Then, you use my code like:

    WebUI.navigateToUrl(yourEmailMessageContent, "${yourLinkXPath}/@href");
    

    To be fair, email messages can take some time to hit inboxes. Hence you might also want to have some retry logic in place. Here is example from my real project code base:

    import java.util.concurrent.TimeUnit
    // ...rest of imports
    
    public final class EmailUtils { 
    
        //...rest of code base
    
        public static String ExtractSignUpLink() {
            String link;
    
            int retryAttempts;
    
            ActionHandler.Handle({
                link = this.ProcessHTML(this.GetLatestMessageBody(30),
                        "//a[.//div[@class = 'sign-mail-btn-text']]/@href");
            }, { boolean success, ex ->
                if (success)
                    return;
                // handle ex
                if (((GoogleJsonResponseException)ex).getDetails().getCode() >= 400)
                    throw ex;
                sleep(1000 * 2**retryAttempts++);
            }, TimeUnit.MINUTES.toSeconds(15))
    
            return link;
        }
    
        //...rest of code base
    }
    
    public final class ActionHandler {
        public static void Handle(Closure onAction, Closure onDone, long timeOut) {
            long startTime = System.currentTimeSeconds();
            while (System.currentTimeSeconds() < startTime + timeOut) {
                try {
                    onDone(true, onAction());
                    return;
                } catch (Exception ex) {
                    onDone(false, ex);
                }
            }
        }
    }