protected void assertSaveButtonEnabled() throws Exception {
		waitForSwing();
		DockingActionIf saveAction = getAction(plugin, "Save Script");

		boolean isEnabled = saveAction.isEnabledForContext(editor.getActionContext(null));
		if (!isEnabled) {
			// the action is enabled when the provider detects changes; it is disabled for read-only

			if (isReadOnly(testScriptFile)) {
				Msg.error(this,
					"Cannot edit a read-only script: " + testScriptFile.getAbsolutePath());
				Msg.error(this, "Script cannot be in a 'system root'; those are: ");
				Collection<ResourceFile> roots = Application.getApplicationRootDirectories();
				for (ResourceFile resourceFile : roots) {
					String root = resourceFile.getCanonicalPath().replace('\\', '/');
					Msg.error(this, "\troot: " + root);
				}
				fail("Unexpected read-only script (see log)");
			}

			//
			// inside knowledge; brittle code
			// 
			@SuppressWarnings("unchecked")
			FixedSizeStack<UndoableEdit> undoStack =
				(FixedSizeStack<UndoableEdit>) getInstanceField("undoStack", editor);
			if (undoStack.isEmpty()) {

				JTextComponent editTextComponent = grabScriptEditorTextArea();
				String text = getText(editTextComponent);
				fail("No undo items for the script editor--did edit take place?  Editor text: " +
					text);
			}

			Boolean isMissing = (Boolean) invokeInstanceMethod("isFileOnDiskMissing", editor);
			if (!isMissing) {

				JTextComponent editTextComponent = grabScriptEditorTextArea();
				String text = getText(editTextComponent);
				fail("Expected a deleted file to trigger save button enablement.  Editor text: " +
					text);
			}
		}
	}
Run the last script by pressing the last script button and return output.

@param taskName name for the task listener
@return script output written to the console
@throws Exception on failure, e.g. timeout
Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java