我有一个Katalon Studio代码库,它有一个成员转换脚本,这取决于是否能够从一个Gmail账户中提取注册链接进行测试。在那个Gmail账户(我们称之为KatalonAutoTesting@gmail.com
)上,我设置了OAuth 2.0客户端ID和服务账户,并为服务账户下载了一个gmail-access-credentials.json
。
我的SMDEmailUtils
类对于这一切,被定义为:
public final class SMDEmailUtils {
private static Gmail _GmailInstance;
public static String GetMainEmail() {
if (!GeneralWebUIUtils.GlobalVariableExists('emailID'))
return "dev@xxx-dev.com";
return GlobalVariable.emailID.toString();
}
public static String CreateEmailFor(String firstName, String lastName) {
final String[] mainEmailParts = this.GetMainEmail().split('@');
return "${mainEmailParts[0]}+${firstName}${lastName}@${mainEmailParts[1]}"
.replaceAll("\'", "");
}
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)
sleep(1000 * 2**retryAttempts++);
}, TimeUnit.MINUTES.toSeconds(15))
return link;
}
/**
* Application name.
*/
private static final String AppName = "Gmail Message Accessor";
/**
* Global instance of the JSON factory.
*/
private static final JsonFactory JSONFactory = GsonFactory.getDefaultInstance();
/**
* Directory to store authorization tokens for this application.
*/
private static final String TokensDirectoryPath = "tokens";
/**
* Global instance of the Scopes required by this quickstart.
* If modifying these Scopes, delete your previously saved tokens/ folder.
*/
private static final List<String> Scopes = [GmailScopes.GMAIL_READONLY,];
private static final String CredentialsFilePath = "./gmail-access-credentials.json";
/**
* Creates an authorized Credential object.
*
* @param httpTransport The network HTTP Transport.
* @return An authorized Credential object.
* @throws IOException If the credentials.json file cannot be found.
*/
private static Credential getCredentials(final NetHttpTransport httpTransport)
throws IOException {
// Load client secrets.
InputStream is = new FileInputStream(this.CredentialsFilePath);
if (is == null) {
throw new FileNotFoundException("Resource not found: " + this.CredentialsFilePath);
}
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(this.JSONFactory, new InputStreamReader(is));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, this.JSONFactory, clientSecrets, this.Scopes)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(this.TokensDirectoryPath)))
.setAccessType("offline")
.build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}
public static Gmail GetGmailInstance() {
if (this._GmailInstance == null) {
// Build a new authorized API client service.
final NetHttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
this._GmailInstance = new Gmail.Builder(httpTransport, this.JSONFactory, getCredentials(httpTransport))
.setApplicationName(this.AppName)
.build();
}
return this._GmailInstance;
}
public static String GetLatestMessageBody(int timeOut) {
return this.getContent(this.GetLatestMessage(timeOut));
}
public static Message GetLatestMessage(int timeOut) {
// get the latest thread list
ListThreadsResponse response = this.HandleRequest({
return this.GetGmailInstance()
.users()
.threads()
.list(this.GetMainEmail())
.setQ("is:unread newer_than:1d")
.setIncludeSpamTrash(true)
.execute();
},
{ ListThreadsResponse res -> return !res.getThreads().isEmpty() },
timeOut);
return response.getThreads()
.collect({ Thread thread ->
return this.GetGmailInstance()
.users()
.threads()
.get(this.GetMainEmail(), thread.getId())
.execute()
}).max { Thread thread -> thread.getMessages().last().getInternalDate() }
.getMessages()
.last();
}
/**
* Copied from https://stackoverflow.com/a/58286921
* @param message
* @return
*/
private static String getContent(Message message) {
StringBuilder stringBuilder = new StringBuilder();
try {
getPlainTextFromMessageParts(message.getPayload().getParts(), stringBuilder);
// NOTE: updated by Mike Warren, this was adapted for message that contain URLs in its body
return new String(Base64.getUrlDecoder().decode(stringBuilder.toString()),
StandardCharsets.UTF_8);
} catch (UnsupportedEncodingException e) {
// NOTE: updated by Mike Warren
Logger.getGlobal().severe("UnsupportedEncoding: ${e.toString()}");
return message.getSnippet();
}
}
/**
* Copied from https://stackoverflow.com/a/58286921
* @param messageParts
* @param stringBuilder
*/
private static void getPlainTextFromMessageParts(List<MessagePart> messageParts, StringBuilder stringBuilder) {
for (MessagePart messagePart : messageParts) {
// NOTE: updated by Mike Warren
if (messagePart.getMimeType().startsWith("text/")) {
stringBuilder.append(messagePart.getBody().getData());
}
if (messagePart.getParts() != null) {
getPlainTextFromMessageParts(messagePart.getParts(), stringBuilder);
}
}
}
public static GenericJson HandleRequest(Closure<GenericJson> onDoRequest, Closure<Boolean> onCheckResponse, int timeOut) {
long startTime = System.currentTimeSeconds();
int exponent = 0;
while (System.currentTimeSeconds() < startTime + timeOut) {
GenericJson response = onDoRequest();
if (onCheckResponse(response))
return response;
// wait some time to try again, exponential backoff style
sleep(1000 * 2**exponent++);
}
return null;
}
/**
* **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>
""";
}
}
我正在尝试点击KatalonAutoTesting@gmail.com
收件箱的消息/线程。
然而,当我点击SMDEmailUtils.ExtractSignUpLink()
时,在从OAuth同意屏幕登录后,我遇到了一些403错误,消息是:Delegation denied for [my_email]
我已在OAuth同意屏面设置测试用户步骤中添加[my_email]
作为测试用户。
当我用"me"
代替this.GetMainEmail()
时,它可以工作,但它访问的是 * 我的 * 电子邮件收件箱,而不是KatalonAutoTesting@gmail.com
。
我该怎么做才能补救这一点,并让它工作?
注意:这是工作,正如所写的,直到我遇到一些invalid_grant
问题。我删除了令牌,试图重新创建它,我似乎面临着这个问题,它感觉像我什么也做不了...
1条答案
按热度按时间zzlelutf1#
事实证明,我尝试做的是所谓的委托,经过长时间的思考,没有必要(通过Katalon工作室
GlobalVariable
s(或类似的东西),最终用户可以设置自己的主电子邮件。这是故意的设计决策WAY回到当我第一次在这个工作).这里的委派是一个YAGNI,只有for Google Workspace users才有意义。但是,我的主电子邮件和我的个人电子邮件只是他们自己的独立用户,而不是在任何工作区上。
因此,这里的解决方案是从
tokens
文件夹中删除StoredCredential
,运行用户测试用例,这一次,通过主电子邮件的Chrome配置文件手动处理OAuth同意屏幕。