I am starting to learn AppleScript. I am running into a couple issues that I could not find document for.
script gt
to sms to someone about m
"sms to " & someone & " about " & m
end
to move pix to pos
"moving " & pix & " to " & pos
end
to resize in pix to size
"resize " & pix & " to " & size
end
end
for the to sms handler, I have to add the "to" after the sms.
but I did not see any document mention the to as part of the
directParamName
for the to move handler, it does not seem like
the directParameter need either of, in or even "to"
for the to
resize handler, I have to add either "in" or "of", or it won't work.
if I add "to", it will complain "to" is used twice.
my question is why the "to sms" handler can have the "to", "to resize" handler must have either "in or of" while the "to move" handler needs none of these?
Thanks!
Here is the document I read a couple times but could not find any reference to any of this
Basically labeled parameters must contain a preposition and an argument.
Direct parameters are indicated by keyword of or in. In this case at least one parameter must follow. The documentation says
directParamName
An identifier for the direct parameter variable. If it is included,
directParamName must be listed immediately after the command name. The
word of or in before directParamName is required in user-defined
handlers, but is optional in terminology-defined handlers (for
example, those defined by applications).
If a user-defined handler includes a direct parameter, the handler
must also include at least one variable parameter.
Regarding your notes:
The preposition to as a labeled parameter is not documented but seems to work.
move is a reserved word of the Standard Suite and expects itself particular parameters. Do not use move as a handler name.
follows the rule about direct parameter. size could be also a reserved word depending on the installed Scripting Additions.
Actually Script Editor shows by syntax coloring the kind of a word. Green words can be safely used as variable names.
Related
PreNote: I am open and hungry for any information, advice, tip etc.
Hello Everyone!
I am trying to create automation with applescript. This is my first personal applescript task but I have some valuable questions. Basically I am trying to catch live notifications from a website and display them in mac os notification.
I am trying to build process for a few days but I don't want to give a mess to you :) so I have roughly explained my process below.
(* Variables used in whole process
set $webToCheck > This is Safari webpage which I want to run my script on it. It won't be front window, script should be run with its name or other property.
set $theClass > This is class name of DOM element to check if it is exist or not. This class is not always exist on DOM of $webpage. It comes with notifications so when use it in "do Javascript" I got error "variable is not defined"
set $num > number of class to use in "do Javascript"
set $input > variable to assign HTML text
set $modifiedInput > Text of input seperated from HTML tags
*)
-- Step 1
tell application "Safari"
work on $webToCheck
-- Step 2
repeat until $input is not empty
set input do Javascript
document.getElementsByClassName > $theClass, $num of $webToCheck
end repeat
-- Step 3
modify text of $input to seperate from RAW HTML -- For example: <a class="" value=""> TEXT to be seperated </a>
Display notification $modifiedInput
-- Step 4
Go back to step 1 or 2 to check and display notification again
First of all, here are some general tips though:
Applescript won't accept $ at the start of variable names.
The variable assignment you are looking for is set {variable} to {value}. You can optionally at the end of it clarify the variable's class using as {class} at the end of the assignment.
Focusing a certain website does not happen with work on {URL} but as with most object oriented things in Applescript with the tell-statement. It will be shown in the full solution.
Text concatenation in Applescript happens with &. So something like "Hello " & "World" is the standard way to do it.
Modification of most things in Applescript happens with set.
It is easier to use innerText instead of innerHTML as splitting text in Applescript is a bit of a pain.
There is no goto but you could wrap the first few steps into a function, which are declared with on or to in Applescript.
Here is the full code with some documentation sprinkled in there:
global webToCheck, theClass, num, input --This says that all variables can be used even in functions.
set webToCheck to "youtube.com" --Strings can only use double quotes.
set theClass to "style-scope yt-alert-with-actions-renderer" --I will use an actual demo to prove that it is working
set num to 0 as integer -- This is the type declaration I was talking about. For numbers we have integer, real(float) and number.
set input to "" -- You don't have define everything at the top, but I will do so for this.
on displayNotification()
tell application "Safari"
tell window 1 -- This will target only the first window. For multiple windows you would have to write a repeat with-loop(for-loop), which I'm not going to do, for the sake of simplicity.
tell (first tab whose URL contains webToCheck) -- This targets just the first tab which contains the webToCheck variable.
set input to do JavaScript "document.getElementsByClassName('" & theClass & "')[" & num & "].innerText" -- This is the way I would go about writing the Javascript. I think you had something different in mind, but this works for my example.
display notification (paragraph 1 of input) with title webToCheck -- This displays the first line of my input since that is the relevant part. I also set a title so I doesn't just say "Script Editor"
end tell
end tell
end tell
end displayNotification
repeat 4 times -- I think this is quite obvious. Adjust this to your needs.
displayNotification()
delay 4
end repeat
Running this while having not used youtube on Safari in a while it displays this:
Note that this isn't the most elegant solution, but I think it is readable and it (hopefully) works for your needs.
I'm trying to get Word to fill in cells in a table. The script works when run as a macro from within Word, but fails when saved as a .vbs file and double-clicked, or run with wscript. This is a part of it.
set obj = GetObject(,"Word.Application)
With obj
With .Selection
MsgBox .text
If (.Information(wdWithInTable) = True) Then
.Collapse Direction:=wdCollapseStart
tCols = .Tables(1).Columns.Count
tRow = .Information(wdStartOfRangeRowNumber)
tCol = .Information(wdStartOfRangeColumnNumber)
For I = 2 To 5
.Tables(1).Cell(tRow, I).Range.Text = "fred" & Str(I)
Next
` now make new row
For I = 1 To tCols - tCol + 1
.MoveRight unit:=wdCell
Next
End If
End With
End With
I have three problems. First, it won't compile unless I comment out the .Collapse and .MoveRight lines. Second, although the MsgBox .text displays the selected text, I get "out of range" errors if I try to access any .Information property.
I'm sure I'm missing something very simple: I usually write software for Macs, and I'd do this using AppleScript. This is my first attempt at getting anything done under Windows.
VBScript and VBA are different languages.
They are a bit similar, but not very. Moreover, VBScript is not like AppleScript; it doesn't let you easily interface with running programs.
The interfaces you'll get from VBScript can behave subtly differently in VBA and VBScript. However, I think you've got two problems here:
:= is invalid syntax in VBScript; you'll need to find an alternative way of calling the function. Try just using positional arguments.
You've no guarantee that this will open the expected file; there could be another instance of Word that it's interacting with instead.
Since your code is not running within the Word environment it would require a reference to the Word object library in order to use enumeration constants (those things that start with wd).
VBScript, however, cannot work with references, which means the only possibility is to use the long value equivalents of the enumerations. You'll find these in the Word Language References. Simplest to use is probably the Object Browser in Word's VBA Editor. (In Word: Alt+F11 to open the VBA Editor; F2 to start the Object Browser; type in the term in the "Search" box, click on the term, then look in the bottom bar.)
The code in the question uses, for example:
wdWithInTable
wdCollapseStart
wdStartOfRangeRowNumber
wdStartOfRangeColumnNumber
wdCell
The reason you get various kinds of errors depends on where these are used.
Also, VBScript can't used named parameters such as Unit:=. Any parameters must be passed in comma-delimited format, if there's more than one, in the order specified by the method or property. If there are optional parameters you don't want to use these should be left "blank":
MethodName parameter, parameter, , , parameter
I am trying to create an AppleScript that can find text on a webpage and tell me the amount of matches I received(Command + F).
I already know how to do the "Find" part:
tell application "System Events"
delay 0.5
keystroke "f" using {command down}
end tell
However, I do not know how to interpret these results, such as tell me whether there is a match, or how many matches I have.
Is there any way to do this?(If it seems a bit vague, I can be more specific)
Thanks!
I agree with #user3439894 and his sentiments about using UI scripting (that is—in this case—getting System Events to issue mouse clicks and keypresses on your behalf). Although it has its uses in other areas, it's by far and away my personal least favourite method to achieve a goal, and one of last resort.
Two very quick reasons why it can be a fragile implementation is: 1) the CmdF shortcut used to initiate the Find... menu command could change, either by your own doing, or if it were to be overridden by a systemwide shortcut that supersedes Safari's claim to it (in fact, for this reason, I would personally trigger the Find... command via the menu bar, which System Events can click on your behalf. Menu items tend not to change like shortcuts, unless regional language settings do); and 2) if Safari loses focus during the time the keypresses are issued and the search is initiated, it messes up the whole command flow in your script, and will at best give you no results, but more likely, throw an error in a later part of the script.
I'm going to demonstrate two alternative methods of searching a Safari webpage for a piece of text, and obtaining the number of times it matches throughout the whole document.
1. Safari's do JavaScript AppleScript command
Most modern web browsers have the ability to run JavaScript code inside their tabs, and Safari can too. AppleScript can take command of this very useful function, provided you give it permission to do so by going into the Develop menu of Safari and ticking Allow JavaScript from Apple Events and Allow Remote Automation (the latter of which will already be on, I'm guessing). There's another menu item called Allow JavaScript from Smart Search Field—I would advise you keep this one off, otherwise it could potentially allow naughty websites to issue commands to your computer and cause mischief.
use A : application "Safari"
set D to the front document of A
set s to "AppleScript" -- the search string
try
set n to (do JavaScript ¬
"document" & ¬
".body" & ¬
".innerText" & ¬
".match(/" & s & "/ig)" & ¬
".length;" in D) as integer -- the number of matches
on error
set n to 0
end try
To break this down: s is the search string that you would otherwise be typing into the search box. It is fed into a JavaScript command, that has the following components:
document: a reference to the Safari webpage document;
body: the body of the document, as opposed to, say, the header or the footer. It's the main bulk of the webpage that users see in front of them;
innerText: the text contained within the body of the document, free of any HTML formatting, but preserving whitespace;
match(): a method or function in JavaScript where the search string s is used to perform a search within the innerText and return an array listing all of the matches;
length: a property of the array returned by match() that reports how many elements is contains, and this equates to the number of matches found during the search.
It's all one command, which, written in full on a single line, looks like this (using the search string "AppleScript"):
document.body.innerText.match(/AppleScript/ig).length;
It returns a number, which is stored in the variable n, and that's it.
This is my favourite method that I would elect to use myself, as it's unlikely to break, and it's nice and fast.
I should point out that match() actually searches and matches using a Regular Expression. I won't go into them right now, but it means that the search string s will need to be a little careful if using any special characters:
\ [ ] { } ^ $ . | ? * + ( )
All you need to be aware of is that, if your search string uses any of these characters, you should precede it with a double-backslash \\. So, for example, if I wanted to search for "matches I received(Command + F)" (which uses (, ) and +), then I would declare my variable s as:
set s to "matches I received\\(Command \\+ F\\)"
2. Chop & Measure
This method is useful if you don't wish to enable Remote JavaScript in your browser, or simply want something that's straightforward to remember and implement off the top of your head next time.
It's simple text manipulation, using AppleScript's text item delimiters and a bit of counting:
use A : application "Safari"
set D to the front document of A
set s to "AppleScript" -- the search string
set T to text of D -- the webpage text content
set l to {initialValue:length of T, finalValue:missing value}
set the text item delimiters to s
set T to text items of T
set the text item delimiters to ""
set T to T as text
set l's finalValue to length of T
set |𝚫l| to (l's initialValue) - (l's finalValue)
set n to |𝚫l| / (length of s)
Safari has a useful AppleScript property called text, which refers to the text content of the specified document or tab (it also has another property called source that contains the HTML source of the document or tab).
Here's the breakdown:
The value of Safari's text property—which is the text content of the webpage—is stored in a variable, T;
The length of T is read and stored. This equates to the number of characters on the whole webpage;
The text item delimiters are set to the search string, s, (which does not need to worry about special characters, so don't insert unnecessary backslashes in this one). The text item delimiters basically erase all occurrences of s from within T;
Then the length of T is read again. If s found any matches in T, it means that the length of T—the number of characters—will have reduced;
It will have reduced by the number of characters in s for each match that occurred. Therefore, turning the equation round a bit, the number of matches, n, is equal to the change in length of T divided by the length of s.
There are other ways to search a webpage with AppleScript, JavaScript, bash, etc., but I think these two serve as reasonable examples of how to achieve the same goal using very different methods. I refer to them as examples, because you might need to make small adjustments to the script to cater for your own needs, such as inserting backslashes where necessary in the first example, or considering in the second how you'd handle the situation if you set s to be an empty string "" (it will throw an error, but this is easily managed).
They also both return real values for n, i.e. 11.0. It's easy to see why in the second example, but I assume it's just a type conversion between JavaScript and AppleScript in the first example (I don't know). Therefore, purely for neatness, I would then coerce the returned value into an integer as I did in the first one, so it reads 11 instead of 11.0:
set n to (...) as integer
but you don't have to.
First of all I must say that UI Scripting can be messy and unreliable. I'd suggest you find a different way to accomplish whatever the real goal is.
That said, using Safari in macOS High Sierra 10.13.3 set to this web page, the following example AppleScript code will set the variable theSearchResult to the result of the search for the word "vague":
tell application "Safari" to activate
delay 0.5
tell application "System Events"
keystroke "f" using command down
delay 0.2
keystroke "vague"
delay 0.2
set theSearchResult to (value of static text 1 of group 2 of tab group 1 of splitter group 1 of window 1 of application process "Safari")
delay 0.2
key code 53 -- # Esc
end tell
return theSearchResult
Result:
"1 match"
Note that the value of the delay commands may need to be adjusted for your system, and or additional delay commands may or may not be needed. Adjust values of and or add/remove the delay commands as appropriate.
The search result can be one of the following, Not found or an integer followed by the word match, e.g. 1 match, and possibly something else, not sure as I've not done extensive testing.
How you want to interpret the result is up to you. You could use a if statement on the theSearchResult, e.g.:
if theSearchResult contains "Not found" then
-- # Do something.
-- # Your code goes here.
else
-- # Do something else.
-- # Your code goes here
end if
Another factor to consider is how is it being searched, i.e. Starts With or Contains. I believe the default in for Safari in macOS High Sierra 10.13.3 is Starts With.
Note: The example AppleScript code is just that and does not employ any error handling and is meant only to show one of many ways to accomplish a task. The onus is always upon the User to add/use appropriate error handling as needed/wanted.
I usually report the events when it passes or fails like:
Reporter.ReportEvent micPass," Title of the passed Event", " Description of Passed Event"
And
Reporter.ReportEvent micFail," Title of the failed Event", " Description of Failed Event"
Is it a good idea to hard code the line number in the title or description of the event like ?
Reporter.ReportEvent micFail," Test Failed at Line 432 ", " Please check line 432 "
To answer the 2nd part of your question, hard-coding will not be a good idea. If you hard-code the line number and in the future, a few lines get added to the code above that line number, the whole point hard-coding goes in vain as the report will now contain the incorrect-line number(which was hard-coded).
And for the first part, I can understand why you want the line-number to be reported. But I don't think there is any "predefined" way of doing it.
But, you can make your own function to get the line number. Here's an idea that you can implement:
Go to the location where your QTP test is stored. Get the full path of the file script.mts(you can find it inside the Action1 Folder). Also, this path can be constructed dynamically if you have good framework and proper folder structure.
After getting the path, you can open that file(script.mts) as a text stream(using the filesystemobject) in Read only mode.
Now you can traverse through the file line-by-line searching for the text "Reporter.ReportEvent micPass," Title of the passed Event", " Description of Passed Event"". After you have found it, you can easily get the line number and return that from your function to store it in some variable which you can use while reporting.
I have not tried this method myself but would have surely given it a try, If I really needed it.
Hardcoding is not a good idea from an end user perspective. In case it's a regression suite it could be used by a tester who doesn't know how to debug the code or analyze. To get the root cause of the failure through reporting (micPass/micFail) you can further refine your scripts
I am trying to loop through all controls in a form:
For Each ctrl in Me.Controls
in order enable/disable the control based on some conditions.
But there is a control on the form that gives an error when I try to access it. What kind of control does that, and how do I find it?
When you get your error and click Debug, is the error on the line setting a control's Enabled property?
If so, add a Debug.Print statement writing out the control's name. Do so on the line before setting the Enabled property.
Here's what I mean:
Dim ctrl As Control
For Each ctrl In Me.Controls
Debug.Print ctrl.Name
ctrl.Enabled = True
Next
The Debug.Print statement will write out to the Immediate Window the name of the control that was last processed in the loop, presumably the one that caused your error.
EDIT
This might work. Put this control in a Panel control and set the Panel's Enabled property to False. If I recall correctly, in VB6 setting a container control's Enabled property to False will also set the container's child controls Enabled to False. If your control's Enabled property really is read-only, I'm curious what would happen.
Try this:
Dim ctr As Control
Dim CtrStatus Boolean
CtrStatus = False
For Each ctr In Me.Controls
If (SSTab.hwnd = GetParent(ctr.hwnd)) Then
Call CallByName(ctr, "Enabled", VbLet, CtrStatus)
else
ctr.Enabled = CtrStatus
End If
Next
Another approach is as follows, that also works at runtime (as opposed to just in the IDE):
private sub SetEnabled()
on error goto errHandle
Dim ctrl As Control
For Each ctrl In Me.Controls
ctrl.Enabled = True
Next
exitPoint:
exit sub
errHandle:
MsgBox "Error " & err.Description & " with Control " & ctrl.Name
resume exitPoint
end sub
Suppress the error reports before the loop and then set it back to standard error handling:
On Error Resume Next
For Each ctrl In Me.Controls
ctrl.Enabled = lIsEnabled
Next
On Error GoTo 0
OR name your controls with a standard prefix/suffix that you can check by name and skip in the loop.
For Each ctrl In Me.Controls
If Left(ctrl.Name, 3) = "sst" Then
ctrl.Enabled = lIsEnabled
End If
Next
Tosa: from your comment on AngryHacker's answer, I think you are checking the container incorrectly.
Your code is like this
' BAD CODE '
If ctrl.Container = fraMovies Then
For me that gives error 450 Wrong number of arguments or invalid property assignment. Do you get the same error?
The code should use Is rather than =, like this
' GOOD CODE '
If ctrl.Container Is fraMovies Then
Explanation. You want to check whether two variables "point" to the same control. Controls are objects: you must use Is not = to check whether two object variables "point" to the same object. This is a classic pitfall in VB6.
One last word. Next time, could you try to post 10 lines or less of actual code, reproducing the error, and give the exact error number and message and the exact line on which it occurs? It really does make it much easier for us to solve your problem - I know it's work for you to shorten the code, but you'll get better answers that way.
EDIT Welcome back! :) You said some controls don't have a Container property. You could try wrapping the test in On Error Resume Next, something like this.
' GOOD CODE '
Dim bMatch As Boolean
Dim ctrl As Control
For Each ctrl In Me.Controls
bMatch = False
On Error Resume Next
bMatch = (ctrl.Container Is fraMovies)
On Error Goto 0
If bMatch Then
ctrl.Enabled = True
End If
Next
To avoid such problems follow the given rules while naming contols
When you name an element in your Visual Basic application, the first character of that name must be an alphabetic character or an underscore.
**Begin each separate word in a name with a capital letter, as in FindLastRecord and RedrawMyForm.
Begin function and method names with a verb, as in InitNameArray or CloseDialog.
Begin class, structure, module, and property names with a noun, as in EmployeeName or CarAccessory.
Begin interface names with the prefix "I", followed by a noun or a noun phrase, like IComponent, or with an adjective describing the interface's behavior, like IPersistable. Do not use the underscore, and use abbreviations sparingly, because abbreviations can cause confusion.
Begin event handler names with a noun describing the type of event followed by the "EventHandler" suffix, as in "MouseEventHandler".
In names of event argument classes, include the "EventArgs" suffix.
If an event has a concept of "before" or "after," use a suffix in present or past tense, as in "ControlAdd" or "ControlAdded".
For long or frequently used terms, use abbreviations to keep name lengths reasonable, for example, "HTML", instead of "Hypertext Markup Language". In general, variable names greater than 32 characters are difficult to read on a monitor set to a low resolution. Also, make sure your abbreviations are consistent throughout the entire application. Randomly switching in a project between "HTML" and "Hypertext Markup Language" can lead to confusion.
Avoid using names in an inner scope that are the same as names in an outer scope. Errors can result if the wrong variable is accessed. If a conflict occurs between a variable and the keyword of the same name, you must identify the keyword by preceding it with the appropriate type library. For example, if you have a variable called Date, you can use the intrinsic Date function only by calling DateTime.Date.